import { Effect, Context, Layer } from "effect";
import { DatabaseError } from "../errors.js";
import { MemoryDatabase } from "../database.js";

// ─── Types ─────────────────────────────────────────────────────────────────

/**
 * Mirror of AgentDebrief from @reactive-agents/runtime.
 * Defined inline to avoid a circular package dependency
 * (runtime depends on memory, not the other way around).
 */
export interface AgentDebriefShape {
  outcome: string;
  summary: string;
  keyFindings: readonly string[];
  errorsEncountered: readonly string[];
  lessonsLearned: readonly string[];
  confidence: string;
  toolsUsed: readonly { name: string; calls: number; successRate: number }[];
  metrics: { tokens: number; duration: number; iterations: number; cost: number };
  rationale: readonly {
    readonly iteration: number;
    readonly decision: string;
    readonly toolName?: string;
    readonly rationale: { readonly why: string; readonly refs?: readonly string[]; readonly confidence?: number };
  }[];
  markdown: string;
}

export interface DebriefRecord {
  taskId: string;
  agentId: string;
  taskPrompt: string;
  terminatedBy: string;
  output: string;
  outputFormat: string;
  debrief: AgentDebriefShape;
  createdAt: number;
}

export interface SaveDebriefInput {
  taskId: string;
  agentId: string;
  taskPrompt: string;
  terminatedBy: string;
  output: string;
  outputFormat: string;
  debrief: AgentDebriefShape;
}

// ─── Service Tag ───────────────────────────────────────────────────────────

export class DebriefStoreService extends Context.Tag("DebriefStoreService")<
  DebriefStoreService,
  {
    /** Persist a completed agent run debrief. */
    readonly save: (input: SaveDebriefInput) => Effect.Effect<void, DatabaseError>;

    /** Look up a debrief by the task ID that produced it. */
    readonly findByTaskId: (
      taskId: string,
    ) => Effect.Effect<DebriefRecord | null, DatabaseError>;

    /** List recent debriefs for an agent, newest first. */
    readonly listByAgent: (
      agentId: string,
      limit: number,
    ) => Effect.Effect<DebriefRecord[], DatabaseError>;
  }
>() {}

// ─── Live Layer ─────────────────────────────────────────────────────────────

export const DebriefStoreLive: Layer.Layer<
  DebriefStoreService,
  DatabaseError,
  MemoryDatabase
> = Layer.effect(
  DebriefStoreService,
  Effect.gen(function* () {
    const db = yield* MemoryDatabase;

    // Create table + indexes if not yet present (safe: IF NOT EXISTS)
    yield* db.exec(
      `CREATE TABLE IF NOT EXISTS agent_debriefs (
        id               TEXT PRIMARY KEY,
        task_id          TEXT NOT NULL,
        agent_id         TEXT NOT NULL,
        created_at       INTEGER NOT NULL,
        task_prompt      TEXT NOT NULL,
        terminated_by    TEXT NOT NULL,
        output           TEXT NOT NULL,
        output_format    TEXT NOT NULL,
        debrief_json     TEXT NOT NULL,
        debrief_markdown TEXT NOT NULL,
        tokens_used      INTEGER,
        duration_ms      INTEGER,
        iterations       INTEGER,
        outcome          TEXT NOT NULL
      )`,
      [],
    );
    yield* db.exec(
      `CREATE INDEX IF NOT EXISTS idx_debriefs_agent_id   ON agent_debriefs(agent_id)`,
      [],
    );
    yield* db.exec(
      `CREATE INDEX IF NOT EXISTS idx_debriefs_task_id    ON agent_debriefs(task_id)`,
      [],
    );
    yield* db.exec(
      `CREATE INDEX IF NOT EXISTS idx_debriefs_created_at ON agent_debriefs(created_at DESC)`,
      [],
    );

    const save = (input: SaveDebriefInput): Effect.Effect<void, DatabaseError> =>
      db
        .exec(
          `INSERT INTO agent_debriefs
             (id, task_id, agent_id, created_at, task_prompt, terminated_by,
              output, output_format, debrief_json, debrief_markdown,
              tokens_used, duration_ms, iterations, outcome)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
          [
            `dbrf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
            input.taskId,
            input.agentId,
            Date.now(),
            input.taskPrompt,
            input.terminatedBy,
            input.output,
            input.outputFormat,
            JSON.stringify(input.debrief),
            input.debrief.markdown,
            input.debrief.metrics.tokens,
            input.debrief.metrics.duration,
            input.debrief.metrics.iterations,
            input.debrief.outcome,
          ],
        )
        .pipe(Effect.asVoid);

    const findByTaskId = (
      taskId: string,
    ): Effect.Effect<DebriefRecord | null, DatabaseError> =>
      db
        .query<Record<string, unknown>>(
          `SELECT * FROM agent_debriefs WHERE task_id = ? LIMIT 1`,
          [taskId],
        )
        .pipe(
          Effect.map((rows) => (rows.length > 0 ? rowToRecord(rows[0]!) : null)),
        );

    const listByAgent = (
      agentId: string,
      limit: number,
    ): Effect.Effect<DebriefRecord[], DatabaseError> =>
      db
        .query<Record<string, unknown>>(
          `SELECT * FROM agent_debriefs WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?`,
          [agentId, limit],
        )
        .pipe(Effect.map((rows) => rows.map(rowToRecord)));

    return { save, findByTaskId, listByAgent };
  }),
);

// ─── Helpers ────────────────────────────────────────────────────────────────

function rowToRecord(row: Record<string, unknown>): DebriefRecord {
  return {
    taskId: row.task_id as string,
    agentId: row.agent_id as string,
    taskPrompt: row.task_prompt as string,
    terminatedBy: row.terminated_by as string,
    output: row.output as string,
    outputFormat: row.output_format as string,
    debrief: JSON.parse(row.debrief_json as string) as AgentDebriefShape,
    createdAt: row.created_at as number,
  };
}
