import { sql } from 'drizzle-orm';
import { CommandMetadataConflictError, CommandPayloadHashConflictError } from './errors.js';
import { mapCommandRow, selectCommandByKey } from './shared.js';
import type { ClaimCommandInput, ClaimCommandResult, CommandDb, CommandStatus } from './types.js';

interface InsertedCommandRow {
  id: string;
  command_key: string;
  command_type: string;
  aggregate_type: string;
  aggregate_id: string;
  payload_hash: string;
  payload: unknown;
  status: CommandStatus;
  claimed_by: string;
  claimed_at: Date | string;
  claim_generation: number | string;
  lease_expires_at: Date | string;
  completed_at: Date | string | null;
  failed_at: Date | string | null;
  result_payload: unknown;
  failure_code: string | null;
  failure_message: string | null;
}

type InsertedCommandRowResult = { rows: InsertedCommandRow[] };

export async function claimCommand(
  db: CommandDb,
  input: ClaimCommandInput,
): Promise<ClaimCommandResult> {
  const inserted = (await db.execute(sql`
    INSERT INTO "command_records" (
      "command_key",
      "command_type",
      "aggregate_type",
      "aggregate_id",
      "payload_hash",
      "payload",
      "status",
      "claimed_by",
      "claimed_at",
      "claim_generation",
      "lease_expires_at"
    )
    VALUES (
      ${input.commandKey},
      ${input.commandType},
      ${input.aggregateType},
      ${input.aggregateId}::uuid,
      ${input.payloadHash},
      ${JSON.stringify(input.payload)}::jsonb,
      'CLAIMED',
      ${input.claimedBy},
      CURRENT_TIMESTAMP,
      1,
      CURRENT_TIMESTAMP + INTERVAL '10 minutes'
    )
    ON CONFLICT ("command_key") DO NOTHING
    RETURNING
      "id",
      "command_key",
      "command_type",
      "aggregate_type",
      "aggregate_id",
      "payload_hash",
      "payload",
      "status",
      "claimed_by",
      "claimed_at",
      "claim_generation",
      "lease_expires_at",
      "completed_at",
      "failed_at",
      "result_payload",
      "failure_code",
      "failure_message"
  `)) as InsertedCommandRowResult;

  const insertedRow = inserted.rows[0];
  if (insertedRow) {
    return { fresh: true, record: mapCommandRow(insertedRow) };
  }

  const existing = await selectCommandByKey(db, input.commandKey);
  if (!existing) {
    throw new Error(`command record disappeared for key ${input.commandKey}`);
  }
  if (existing.commandType !== input.commandType) {
    throw new CommandMetadataConflictError(input.commandKey, 'commandType');
  }
  if (existing.aggregateType !== input.aggregateType) {
    throw new CommandMetadataConflictError(input.commandKey, 'aggregateType');
  }
  if (existing.aggregateId !== input.aggregateId) {
    throw new CommandMetadataConflictError(input.commandKey, 'aggregateId');
  }
  if (existing.payloadHash !== input.payloadHash) {
    throw new CommandPayloadHashConflictError(input.commandKey);
  }
  return { fresh: false, record: existing };
}
