import Database from "better-sqlite3";
import { mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { v4 as uuid } from "uuid";
import type {
  CalendarRecord,
  EventRecord,
  EventPersonRecord,
  ReminderRecord,
  EventWithPeople,
  CreateEventRequest,
  UpdateEventRequest,
  GoogleCachedEventRecord,
} from "./types.js";

const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_DIR = join(__dirname, "..", "data");
const DB_PATH = join(DATA_DIR, "calendar.db");

let db: Database.Database;

export function initDb(): void {
  mkdirSync(DATA_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 calendars (
      id TEXT PRIMARY KEY,
      name TEXT NOT NULL,
      type TEXT NOT NULL CHECK(type IN ('system', 'family')),
      color TEXT NOT NULL,
      created_at TEXT NOT NULL DEFAULT (datetime('now'))
    );

    CREATE TABLE IF NOT EXISTS events (
      id TEXT PRIMARY KEY,
      calendar_id TEXT NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,
      title TEXT NOT NULL,
      description TEXT,
      notes TEXT,
      start_time TEXT NOT NULL,
      end_time TEXT NOT NULL,
      all_day INTEGER NOT NULL DEFAULT 0,
      location TEXT,
      recurrence_rule TEXT,
      created_by TEXT NOT NULL,
      created_at TEXT NOT NULL DEFAULT (datetime('now')),
      updated_at TEXT NOT NULL DEFAULT (datetime('now'))
    );

    CREATE TABLE IF NOT EXISTS event_people (
      event_id TEXT NOT NULL REFERENCES events(id) ON DELETE CASCADE,
      person_name TEXT NOT NULL,
      invite_preference TEXT NOT NULL DEFAULT 'none'
        CHECK(invite_preference IN ('calendar_invite', 'reminder_only', 'both', 'none')),
      invite_sent INTEGER NOT NULL DEFAULT 0,
      reminder_sent INTEGER NOT NULL DEFAULT 0,
      google_event_id TEXT,
      PRIMARY KEY (event_id, person_name)
    );

    CREATE TABLE IF NOT EXISTS reminders (
      id TEXT PRIMARY KEY,
      event_id TEXT NOT NULL REFERENCES events(id) ON DELETE CASCADE,
      person_name TEXT NOT NULL,
      remind_at TEXT NOT NULL,
      sent INTEGER NOT NULL DEFAULT 0,
      sent_at TEXT
    );

    CREATE TABLE IF NOT EXISTS google_cached_events (
      id TEXT PRIMARY KEY,
      google_calendar_id TEXT NOT NULL,
      google_calendar_name TEXT NOT NULL,
      google_event_id TEXT NOT NULL,
      title TEXT NOT NULL,
      description TEXT,
      start_time TEXT NOT NULL,
      end_time TEXT NOT NULL,
      all_day INTEGER NOT NULL DEFAULT 0,
      location TEXT,
      person_email TEXT NOT NULL,
      last_synced TEXT NOT NULL DEFAULT (datetime('now'))
    );

    CREATE INDEX IF NOT EXISTS idx_events_calendar ON events(calendar_id);
    CREATE INDEX IF NOT EXISTS idx_events_start ON events(start_time);
    CREATE INDEX IF NOT EXISTS idx_events_end ON events(end_time);
    CREATE INDEX IF NOT EXISTS idx_reminders_due ON reminders(remind_at, sent);
    CREATE INDEX IF NOT EXISTS idx_google_cached_calendar ON google_cached_events(google_calendar_id);
    CREATE INDEX IF NOT EXISTS idx_google_cached_person ON google_cached_events(person_email);
  `);

  const count = db.prepare("SELECT COUNT(*) as c FROM calendars").get() as { c: number };
  if (count.c === 0) {
    const insert = db.prepare("INSERT INTO calendars (id, name, type, color) VALUES (?, ?, ?, ?)");
    insert.run(uuid(), "System", "system", "#3b82f6");
    insert.run(uuid(), "Family", "family", "#10b981");
  }
}

export function getCalendars(): CalendarRecord[] {
  return db.prepare("SELECT * FROM calendars ORDER BY type, name").all() as CalendarRecord[];
}

export function getCalendar(id: string): CalendarRecord | undefined {
  return db.prepare("SELECT * FROM calendars WHERE id = ?").get(id) as CalendarRecord | undefined;
}

export function getEvents(params: {
  calendar_id?: string;
  start?: string;
  end?: string;
  person?: string;
}): EventWithPeople[] {
  let query = "SELECT * FROM events WHERE 1=1";
  const bindings: unknown[] = [];

  if (params.calendar_id) {
    query += " AND calendar_id = ?";
    bindings.push(params.calendar_id);
  }
  if (params.start) {
    query += " AND end_time >= ?";
    bindings.push(params.start);
  }
  if (params.end) {
    query += " AND start_time <= ?";
    bindings.push(params.end);
  }

  query += " ORDER BY start_time ASC";

  let events = db.prepare(query).all(...bindings) as EventRecord[];

  if (params.person) {
    const eventIds = events.map((e) => e.id);
    if (eventIds.length > 0) {
      const placeholders = eventIds.map(() => "?").join(",");
      const personEvents = db
        .prepare(`SELECT event_id FROM event_people WHERE person_name = ? AND event_id IN (${placeholders})`)
        .all(params.person, ...eventIds) as { event_id: string }[];
      const personEventIds = new Set(personEvents.map((pe) => pe.event_id));
      events = events.filter((e) => personEventIds.has(e.id));
    }
  }

  return events.map((event) => enrichEvent(event));
}

export function getEvent(id: string): EventWithPeople | undefined {
  const event = db.prepare("SELECT * FROM events WHERE id = ?").get(id) as EventRecord | undefined;
  if (!event) return undefined;
  return enrichEvent(event);
}

function enrichEvent(event: EventRecord): EventWithPeople {
  const people = db
    .prepare("SELECT * FROM event_people WHERE event_id = ?")
    .all(event.id) as EventPersonRecord[];
  const reminders = db
    .prepare("SELECT * FROM reminders WHERE event_id = ?")
    .all(event.id) as ReminderRecord[];
  return { ...event, people, reminders };
}

export function createEvent(req: CreateEventRequest): EventWithPeople {
  const id = uuid();
  const now = new Date().toISOString();

  const insertEvent = db.prepare(`
    INSERT INTO events (id, calendar_id, title, description, notes, start_time, end_time, all_day, location, recurrence_rule, created_by, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);

  const insertPerson = db.prepare(`
    INSERT INTO event_people (event_id, person_name, invite_preference)
    VALUES (?, ?, ?)
  `);

  const insertReminder = db.prepare(`
    INSERT INTO reminders (id, event_id, person_name, remind_at)
    VALUES (?, ?, ?, ?)
  `);

  const transaction = db.transaction(() => {
    insertEvent.run(
      id,
      req.calendar_id,
      req.title,
      req.description ?? null,
      req.notes ?? null,
      req.start_time,
      req.end_time,
      req.all_day ? 1 : 0,
      req.location ?? null,
      req.recurrence_rule ?? null,
      req.created_by,
      now,
      now
    );

    if (req.people) {
      for (const p of req.people) {
        insertPerson.run(id, p.person_name, p.invite_preference);
      }
    }

    if (req.reminders) {
      for (const r of req.reminders) {
        insertReminder.run(uuid(), id, r.person_name, r.remind_at);
      }
    }
  });

  transaction();
  return getEvent(id)!;
}

export function updateEvent(id: string, req: UpdateEventRequest): EventWithPeople | undefined {
  const existing = db.prepare("SELECT * FROM events WHERE id = ?").get(id) as EventRecord | undefined;
  if (!existing) return undefined;

  const now = new Date().toISOString();
  const fields: string[] = ["updated_at = ?"];
  const bindings: unknown[] = [now];

  if (req.title !== undefined) { fields.push("title = ?"); bindings.push(req.title); }
  if (req.description !== undefined) { fields.push("description = ?"); bindings.push(req.description); }
  if (req.notes !== undefined) { fields.push("notes = ?"); bindings.push(req.notes); }
  if (req.start_time !== undefined) { fields.push("start_time = ?"); bindings.push(req.start_time); }
  if (req.end_time !== undefined) { fields.push("end_time = ?"); bindings.push(req.end_time); }
  if (req.all_day !== undefined) { fields.push("all_day = ?"); bindings.push(req.all_day ? 1 : 0); }
  if (req.location !== undefined) { fields.push("location = ?"); bindings.push(req.location); }
  if (req.recurrence_rule !== undefined) { fields.push("recurrence_rule = ?"); bindings.push(req.recurrence_rule); }

  const transaction = db.transaction(() => {
    db.prepare(`UPDATE events SET ${fields.join(", ")} WHERE id = ?`).run(...bindings, id);

    if (req.people !== undefined) {
      db.prepare("DELETE FROM event_people WHERE event_id = ?").run(id);
      const insertPerson = db.prepare(
        "INSERT INTO event_people (event_id, person_name, invite_preference) VALUES (?, ?, ?)"
      );
      for (const p of req.people) {
        insertPerson.run(id, p.person_name, p.invite_preference);
      }
    }
  });

  transaction();
  return getEvent(id);
}

export function deleteEvent(id: string): boolean {
  const result = db.prepare("DELETE FROM events WHERE id = ?").run(id);
  return result.changes > 0;
}

function toUTC(isoString: string): string {
  const d = new Date(isoString);
  if (isNaN(d.getTime())) return isoString;
  return d.toISOString();
}

export function getDueReminders(): (ReminderRecord & { event_title: string })[] {
  const all = db
    .prepare(`
      SELECT r.*, e.title as event_title
      FROM reminders r
      JOIN events e ON r.event_id = e.id
      WHERE r.sent = 0
      ORDER BY r.remind_at ASC
    `)
    .all() as (ReminderRecord & { event_title: string })[];

  const nowMs = Date.now();
  return all.filter((r) => {
    const remindMs = new Date(r.remind_at).getTime();
    return !isNaN(remindMs) && remindMs <= nowMs;
  });
}

export function markReminderSent(id: string): void {
  const now = new Date().toISOString();
  db.prepare("UPDATE reminders SET sent = 1, sent_at = ? WHERE id = ?").run(now, id);
}

export function createReminder(eventId: string, personName: string, remindAt: string): ReminderRecord {
  const id = uuid();
  const normalizedRemindAt = toUTC(remindAt);
  db.prepare("INSERT INTO reminders (id, event_id, person_name, remind_at) VALUES (?, ?, ?, ?)").run(
    id,
    eventId,
    personName,
    normalizedRemindAt
  );
  return db.prepare("SELECT * FROM reminders WHERE id = ?").get(id) as ReminderRecord;
}

export function deleteReminder(id: string): boolean {
  const result = db.prepare("DELETE FROM reminders WHERE id = ?").run(id);
  return result.changes > 0;
}

export function upsertGoogleCachedEvents(events: Omit<GoogleCachedEventRecord, "id" | "last_synced">[]): void {
  const now = new Date().toISOString();
  const upsert = db.prepare(`
    INSERT INTO google_cached_events (id, google_calendar_id, google_calendar_name, google_event_id, title, description, start_time, end_time, all_day, location, person_email, last_synced)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
      title = excluded.title,
      description = excluded.description,
      start_time = excluded.start_time,
      end_time = excluded.end_time,
      all_day = excluded.all_day,
      location = excluded.location,
      last_synced = excluded.last_synced
  `);

  const transaction = db.transaction(() => {
    for (const e of events) {
      const id = `${e.google_calendar_id}:${e.google_event_id}`;
      upsert.run(
        id,
        e.google_calendar_id,
        e.google_calendar_name,
        e.google_event_id,
        e.title,
        e.description ?? null,
        e.start_time,
        e.end_time,
        e.all_day,
        e.location ?? null,
        e.person_email,
        now
      );
    }
  });
  transaction();
}

export function getGoogleCachedEvents(params: { start?: string; end?: string; person_email?: string }): GoogleCachedEventRecord[] {
  let query = "SELECT * FROM google_cached_events WHERE 1=1";
  const bindings: unknown[] = [];

  if (params.start) { query += " AND end_time >= ?"; bindings.push(params.start); }
  if (params.end) { query += " AND start_time <= ?"; bindings.push(params.end); }
  if (params.person_email) { query += " AND person_email = ?"; bindings.push(params.person_email); }

  query += " ORDER BY start_time ASC";
  return db.prepare(query).all(...bindings) as GoogleCachedEventRecord[];
}

export function clearGoogleCachedEventsForCalendar(googleCalendarId: string): void {
  db.prepare("DELETE FROM google_cached_events WHERE google_calendar_id = ?").run(googleCalendarId);
}
