import Database from "better-sqlite3";
import path from "path";
import fs from "fs";

const DB_DIR = path.join(process.cwd(), "services", "chronicle");
const DB_PATH = path.join(DB_DIR, "chronicle.db");

let _db: Database.Database | null = null;

function getDb(): Database.Database {
  if (_db) return _db;

  fs.mkdirSync(DB_DIR, { recursive: true });

  _db = new Database(DB_PATH);
  _db.pragma("journal_mode = WAL");
  _db.pragma("foreign_keys = ON");

  _db.exec(`
    CREATE TABLE IF NOT EXISTS chronicle_events (
      id          TEXT PRIMARY KEY,
      date        TEXT NOT NULL,
      timestamp   INTEGER NOT NULL,
      source      TEXT NOT NULL,
      type        TEXT NOT NULL,
      title       TEXT NOT NULL,
      body        TEXT,
      metadata    TEXT,
      created_at  INTEGER DEFAULT (unixepoch() * 1000)
    );

    CREATE INDEX IF NOT EXISTS idx_events_date   ON chronicle_events(date);
    CREATE INDEX IF NOT EXISTS idx_events_source ON chronicle_events(source);
    CREATE INDEX IF NOT EXISTS idx_events_ts     ON chronicle_events(timestamp);

    CREATE TABLE IF NOT EXISTS chronicle_days (
      date                   TEXT PRIMARY KEY,
      narrative              TEXT,
      narrative_generated_at INTEGER
    );
  `);

  return _db;
}

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

export type EventSource = "calendar" | "imessage" | "agent" | "music";

export interface ChronicleEvent {
  id: string;
  date: string;
  timestamp: number;
  source: EventSource;
  type: string;
  title: string;
  body?: string;
  metadata?: Record<string, unknown>;
}

export interface ChronicleDay {
  date: string;
  events: ChronicleEvent[];
  narrative?: string;
  narrativeGeneratedAt?: number;
}

// ─── Events ──────────────────────────────────────────────────────────────────

export function upsertEvent(event: ChronicleEvent): void {
  const db = getDb();
  db.prepare(`
    INSERT OR REPLACE INTO chronicle_events
      (id, date, timestamp, source, type, title, body, metadata)
    VALUES
      (@id, @date, @timestamp, @source, @type, @title, @body, @metadata)
  `).run({
    ...event,
    body: event.body ?? null,
    metadata: event.metadata ? JSON.stringify(event.metadata) : null,
  });
}

export function upsertEvents(events: ChronicleEvent[]): void {
  const db = getDb();
  const stmt = db.prepare(`
    INSERT OR REPLACE INTO chronicle_events
      (id, date, timestamp, source, type, title, body, metadata)
    VALUES
      (@id, @date, @timestamp, @source, @type, @title, @body, @metadata)
  `);
  const run = db.transaction((evts: ChronicleEvent[]) => {
    for (const e of evts) {
      stmt.run({
        ...e,
        body: e.body ?? null,
        metadata: e.metadata ? JSON.stringify(e.metadata) : null,
      });
    }
  });
  run(events);
}

export function getEventsForDate(date: string): ChronicleEvent[] {
  const db = getDb();
  const rows = db.prepare(`
    SELECT * FROM chronicle_events WHERE date = ? ORDER BY timestamp ASC
  `).all(date) as RawEvent[];
  return rows.map(deserializeEvent);
}

export function getEventsSince(since: string): ChronicleEvent[] {
  const db = getDb();
  const rows = db.prepare(`
    SELECT * FROM chronicle_events WHERE date >= ? ORDER BY date DESC, timestamp ASC
  `).all(since) as RawEvent[];
  return rows.map(deserializeEvent);
}

export function searchEvents(query: string, limit = 50): ChronicleEvent[] {
  const db = getDb();
  const like = `%${query}%`;
  const rows = db.prepare(`
    SELECT * FROM chronicle_events
    WHERE title LIKE ? OR body LIKE ?
    ORDER BY timestamp DESC
    LIMIT ?
  `).all(like, like, limit) as RawEvent[];
  return rows.map(deserializeEvent);
}

// ─── Timeline (paginated days) ────────────────────────────────────────────────

export function getTimelineDays(beforeDate: string | null, limit = 7): ChronicleDay[] {
  const db = getDb();

  // Find distinct dates with events
  const before = beforeDate ?? new Date().toISOString().slice(0, 10);
  const dateRows = db.prepare(`
    SELECT DISTINCT date FROM chronicle_events
    WHERE date <= ?
    ORDER BY date DESC
    LIMIT ?
  `).all(before, limit) as { date: string }[];

  if (dateRows.length === 0) return [];

  const dates = dateRows.map((r) => r.date);

  // Fetch events for all those dates in one query
  const placeholders = dates.map(() => "?").join(", ");
  const eventRows = db.prepare(`
    SELECT * FROM chronicle_events
    WHERE date IN (${placeholders})
    ORDER BY timestamp ASC
  `).all(...dates) as RawEvent[];

  // Fetch narratives
  const dayRows = db.prepare(`
    SELECT date, narrative, narrative_generated_at FROM chronicle_days
    WHERE date IN (${placeholders})
  `).all(...dates) as { date: string; narrative: string | null; narrative_generated_at: number | null }[];

  const narrativeMap = new Map(dayRows.map((r) => [r.date, r]));
  const eventsByDate = new Map<string, ChronicleEvent[]>();

  for (const row of eventRows) {
    const e = deserializeEvent(row);
    if (!eventsByDate.has(e.date)) eventsByDate.set(e.date, []);
    eventsByDate.get(e.date)!.push(e);
  }

  return dates.map((date) => {
    const narRow = narrativeMap.get(date);
    return {
      date,
      events: eventsByDate.get(date) ?? [],
      narrative: narRow?.narrative ?? undefined,
      narrativeGeneratedAt: narRow?.narrative_generated_at ?? undefined,
    };
  });
}

export function getEarliestDate(): string | null {
  const db = getDb();
  const row = db.prepare(`
    SELECT MIN(date) as d FROM chronicle_events
  `).get() as { d: string | null };
  return row?.d ?? null;
}

// ─── Narratives ───────────────────────────────────────────────────────────────

export function getNarrative(date: string): { narrative: string; generatedAt: number } | null {
  const db = getDb();
  const row = db.prepare(`
    SELECT narrative, narrative_generated_at FROM chronicle_days WHERE date = ?
  `).get(date) as { narrative: string | null; narrative_generated_at: number | null } | undefined;

  if (!row?.narrative) return null;
  return { narrative: row.narrative, generatedAt: row.narrative_generated_at! };
}

export function upsertNarrative(date: string, narrative: string): void {
  const db = getDb();
  db.prepare(`
    INSERT INTO chronicle_days (date, narrative, narrative_generated_at)
    VALUES (?, ?, ?)
    ON CONFLICT(date) DO UPDATE SET
      narrative = excluded.narrative,
      narrative_generated_at = excluded.narrative_generated_at
  `).run(date, narrative, Date.now());
}

// ─── Internal helpers ─────────────────────────────────────────────────────────

interface RawEvent {
  id: string;
  date: string;
  timestamp: number;
  source: EventSource;
  type: string;
  title: string;
  body: string | null;
  metadata: string | null;
  created_at: number;
}

function deserializeEvent(row: RawEvent): ChronicleEvent {
  return {
    id: row.id,
    date: row.date,
    timestamp: row.timestamp,
    source: row.source,
    type: row.type,
    title: row.title,
    body: row.body ?? undefined,
    metadata: row.metadata ? (JSON.parse(row.metadata) as Record<string, unknown>) : undefined,
  };
}
