import { randomUUID } from "crypto";
import type { ResultSetHeader, RowDataPacket } from "mysql2/promise";
import { isMariaDbEnabled } from "@/lib/db/config";
import { execute, queryRows } from "@/lib/db/pool";
import { promises as fs } from "fs";
import path from "path";
import { parseProjectTimezone } from "./plan-times";

const FIRES_FILE = path.join(process.cwd(), "data", "schedule-fires.json");

interface ProjectFireRecord {
  date: string;
  slots: string[];
}

type FireStore = Record<string, ProjectFireRecord>;

async function readJsonStore(): Promise<FireStore> {
  try {
    const raw = await fs.readFile(FIRES_FILE, "utf-8");
    return JSON.parse(raw) as FireStore;
  } catch {
    return {};
  }
}

async function writeJsonStore(store: FireStore) {
  await fs.mkdir(path.dirname(FIRES_FILE), { recursive: true });
  await fs.writeFile(FIRES_FILE, JSON.stringify(store, null, 2), "utf-8");
}

export function localDateKey(now: Date, timezone: string): string {
  const timeZone = parseProjectTimezone(timezone);
  try {
    return new Intl.DateTimeFormat("en-CA", {
      timeZone,
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
    }).format(now);
  } catch {
    return now.toISOString().slice(0, 10);
  }
}

async function wasSlotFiredJson(
  projectId: string,
  slotLabel: string,
  timezone: string,
  now: Date,
): Promise<boolean> {
  const store = await readJsonStore();
  const date = localDateKey(now, timezone);
  const record = store[projectId];
  return record?.date === date && record.slots.includes(slotLabel);
}

async function markSlotFiredJson(
  projectId: string,
  slotLabel: string,
  timezone: string,
  now: Date,
): Promise<void> {
  const store = await readJsonStore();
  const date = localDateKey(now, timezone);
  const existing = store[projectId];
  const slots =
    existing?.date === date ? [...new Set([...existing.slots, slotLabel])] : [slotLabel];
  store[projectId] = { date, slots };
  await writeJsonStore(store);
}

export async function wasSlotFiredToday(
  projectId: string,
  slotLabel: string,
  timezone: string,
  now = new Date(),
): Promise<boolean> {
  if (isMariaDbEnabled()) {
    try {
      const date = localDateKey(now, timezone);
      const rows = await queryRows<RowDataPacket[]>(
        `SELECT id FROM schedule_slot_fires
         WHERE project_id = ? AND fire_date = ? AND slot_label = ?
         LIMIT 1`,
        [projectId, date, slotLabel],
      );
      if (rows[0]) return true;
    } catch {
      // Table may not exist yet — fall back to JSON.
    }
  }
  return wasSlotFiredJson(projectId, slotLabel, timezone, now);
}

/**
 * Atomically claim a Morning/Evening slot for today.
 * Returns false if another worker already claimed it (prevents duplicate social posts).
 */
export async function tryClaimSlotFire(input: {
  projectId: string;
  slotLabel: string;
  timezone: string;
  now?: Date;
  jobId?: string;
  postId?: string | null;
}): Promise<boolean> {
  const now = input.now ?? new Date();
  const date = localDateKey(now, input.timezone);

  if (input.slotLabel === "Manual") {
    // Manual runs are allowed multiple times, but still record best-effort.
    await markSlotFired(input.projectId, input.slotLabel, input.timezone, now);
    return true;
  }

  if (isMariaDbEnabled()) {
    try {
      const result = await execute(
        `INSERT INTO schedule_slot_fires (id, project_id, fire_date, slot_label, post_id, job_id)
         VALUES (?, ?, ?, ?, ?, ?)`,
        [
          randomUUID(),
          input.projectId,
          date,
          input.slotLabel,
          input.postId ?? null,
          input.jobId ?? null,
        ],
      );
      // Duplicate key → already claimed
      if ((result as ResultSetHeader).affectedRows === 1) {
        await markSlotFiredJson(input.projectId, input.slotLabel, input.timezone, now);
        return true;
      }
      return false;
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      if (/Duplicate|ER_DUP_ENTRY/i.test(message)) {
        return false;
      }
      // Table missing — fall through to JSON claim (best-effort, same process).
    }
  }

  const already = await wasSlotFiredJson(input.projectId, input.slotLabel, input.timezone, now);
  if (already) return false;
  await markSlotFiredJson(input.projectId, input.slotLabel, input.timezone, now);
  return true;
}

export async function markSlotFired(
  projectId: string,
  slotLabel: string,
  timezone: string,
  now = new Date(),
): Promise<void> {
  await tryClaimSlotFire({ projectId, slotLabel, timezone, now });
}

export async function attachPostToSlotFire(input: {
  projectId: string;
  slotLabel: string;
  timezone: string;
  postId: string;
  now?: Date;
}): Promise<void> {
  if (!isMariaDbEnabled() || input.slotLabel === "Manual") return;
  const date = localDateKey(input.now ?? new Date(), input.timezone);
  try {
    await execute(
      `UPDATE schedule_slot_fires SET post_id = ?
       WHERE project_id = ? AND fire_date = ? AND slot_label = ?`,
      [input.postId, input.projectId, date, input.slotLabel],
    );
  } catch {
    // ignore
  }
}
