import { getDb } from "./db";
import type { Keyword } from "../shared/types";

interface CategoryRow {
  id: number;
  name: string;
  parent_id: number | null;
  color: string | null;
  icon: string | null;
  type: "expense" | "income" | "transfer";
  is_active: boolean;
  is_inputable: boolean;
  sort_order: number;
  keyword_count: number;
  i18n_key: string | null;
}

export async function getAllCategoriesWithCounts(): Promise<CategoryRow[]> {
  const db = await getDb();
  return db.select<CategoryRow[]>(
    `SELECT c.*, COUNT(k.id) AS keyword_count
     FROM categories c
     LEFT JOIN keywords k ON k.category_id = c.id AND k.is_active = 1
     WHERE c.is_active = 1
     GROUP BY c.id
     ORDER BY c.sort_order, c.name`
  );
}

export async function getCategoryDepth(categoryId: number): Promise<number> {
  const db = await getDb();
  let depth = 0;
  let currentId: number | null = categoryId;
  while (currentId !== null) {
    const parentRows: Array<{ parent_id: number | null }> = await db.select<Array<{ parent_id: number | null }>>(
      `SELECT parent_id FROM categories WHERE id = $1 AND is_active = 1`,
      [currentId]
    );
    if (parentRows.length === 0 || parentRows[0].parent_id === null) break;
    currentId = parentRows[0].parent_id;
    depth++;
  }
  return depth;
}

export async function createCategory(data: {
  name: string;
  type: string;
  color: string;
  parent_id: number | null;
  is_inputable: boolean;
  sort_order: number;
}): Promise<number> {
  const db = await getDb();

  // Validate max depth: parent at depth 2 would create a 4th level
  if (data.parent_id !== null) {
    const parentDepth = await getCategoryDepth(data.parent_id);
    if (parentDepth >= 2) {
      throw new Error("Cannot create category: maximum depth of 3 levels reached");
    }
  }

  const result = await db.execute(
    `INSERT INTO categories (name, type, color, parent_id, is_inputable, sort_order) VALUES ($1, $2, $3, $4, $5, $6)`,
    [data.name, data.type, data.color, data.parent_id, data.is_inputable ? 1 : 0, data.sort_order]
  );

  // Auto-manage is_inputable: when a child is created under a parent, set parent to is_inputable = 0
  if (data.parent_id !== null) {
    await db.execute(
      `UPDATE categories SET is_inputable = 0 WHERE id = $1 AND is_inputable = 1`,
      [data.parent_id]
    );
  }

  return result.lastInsertId as number;
}

export async function updateCategory(
  id: number,
  data: {
    name: string;
    type: string;
    color: string;
    parent_id: number | null;
    is_inputable: boolean;
    sort_order: number;
  }
): Promise<void> {
  const db = await getDb();
  await db.execute(
    `UPDATE categories SET name = $1, type = $2, color = $3, parent_id = $4, is_inputable = $5, sort_order = $6 WHERE id = $7`,
    [data.name, data.type, data.color, data.parent_id, data.is_inputable ? 1 : 0, data.sort_order, id]
  );
}

export async function getNextSortOrder(parentId: number | null): Promise<number> {
  const db = await getDb();
  const rows = parentId === null
    ? await db.select<Array<{ max_sort: number | null }>>(
        `SELECT MAX(sort_order) AS max_sort FROM categories WHERE is_active = 1 AND parent_id IS NULL`
      )
    : await db.select<Array<{ max_sort: number | null }>>(
        `SELECT MAX(sort_order) AS max_sort FROM categories WHERE is_active = 1 AND parent_id = $1`,
        [parentId]
      );
  return (rows[0]?.max_sort ?? 0) + 1;
}

export async function hasDuplicateSortOrders(): Promise<boolean> {
  const db = await getDb();
  const rows = await db.select<Array<{ cnt: number }>>(
    `SELECT COUNT(*) AS cnt FROM (
       SELECT parent_id, sort_order FROM categories WHERE is_active = 1
       GROUP BY parent_id, sort_order HAVING COUNT(*) > 1
     )`
  );
  return (rows[0]?.cnt ?? 0) > 0;
}

export async function fixDuplicateSortOrders(): Promise<void> {
  const db = await getDb();
  const rows = await db.select<Array<{ id: number; parent_id: number | null }>>(
    `SELECT id, parent_id FROM categories WHERE is_active = 1 ORDER BY parent_id, sort_order, name`
  );

  let currentParentId: number | null | undefined = undefined;
  let seq = 0;
  for (const row of rows) {
    if (row.parent_id !== currentParentId) {
      currentParentId = row.parent_id;
      seq = 0;
    }
    seq++;
    await db.execute(
      `UPDATE categories SET sort_order = $1 WHERE id = $2`,
      [seq, row.id]
    );
  }
}

export async function updateCategorySortOrders(
  updates: Array<{ id: number; sort_order: number; parent_id: number | null }>
): Promise<void> {
  const db = await getDb();
  for (const u of updates) {
    await db.execute(
      `UPDATE categories SET sort_order = $1, parent_id = $2 WHERE id = $3`,
      [u.sort_order, u.parent_id, u.id]
    );
  }
}

export async function deactivateCategory(id: number): Promise<void> {
  const db = await getDb();
  // Remember the parent before deactivating
  const rows = await db.select<Array<{ parent_id: number | null }>>(
    `SELECT parent_id FROM categories WHERE id = $1`,
    [id]
  );
  const parentId = rows[0]?.parent_id ?? null;

  // Promote children to parent level so they don't become orphans
  await db.execute(
    `UPDATE categories SET parent_id = $1 WHERE parent_id = $2`,
    [parentId, id]
  );
  // Only deactivate the target category itself
  await db.execute(
    `UPDATE categories SET is_active = 0 WHERE id = $1`,
    [id]
  );

  // Auto-manage is_inputable: if parent now has no active children, restore is_inputable
  if (parentId !== null) {
    const childCount = await db.select<Array<{ cnt: number }>>(
      `SELECT COUNT(*) AS cnt FROM categories WHERE parent_id = $1 AND is_active = 1`,
      [parentId]
    );
    if ((childCount[0]?.cnt ?? 0) === 0) {
      await db.execute(
        `UPDATE categories SET is_inputable = 1 WHERE id = $1`,
        [parentId]
      );
    }
  }
}

export async function getCategoryUsageCount(id: number): Promise<number> {
  const db = await getDb();
  const rows = await db.select<Array<{ cnt: number }>>(
    `SELECT COUNT(*) AS cnt FROM transactions WHERE category_id = $1`,
    [id]
  );
  return rows[0]?.cnt ?? 0;
}

export async function getChildrenUsageCount(parentId: number): Promise<number> {
  const db = await getDb();
  // Check descendants recursively (up to 2 levels deep)
  const rows = await db.select<Array<{ cnt: number }>>(
    `SELECT COUNT(*) AS cnt FROM transactions WHERE category_id IN (
       SELECT id FROM categories WHERE parent_id = $1 AND is_active = 1
       UNION
       SELECT id FROM categories WHERE parent_id IN (
         SELECT id FROM categories WHERE parent_id = $1 AND is_active = 1
       ) AND is_active = 1
     )`,
    [parentId]
  );
  return rows[0]?.cnt ?? 0;
}

export async function reinitializeCategories(): Promise<void> {
  const db = await getDb();
  // Clear keywords, unlink transactions, delete all categories
  await db.execute("DELETE FROM keywords");
  await db.execute("UPDATE transactions SET category_id = NULL");
  await db.execute("DELETE FROM categories");

  // Re-seed parent categories
  const parents = [
    [1, "Revenus", "income", 1],
    [2, "Dépenses récurrentes", "expense", 2],
    [3, "Dépenses ponctuelles", "expense", 3],
    [4, "Maison", "expense", 4],
    [5, "Placements", "transfer", 5],
    [6, "Autres", "expense", 6],
  ] as const;
  for (const [id, name, type, sort] of parents) {
    await db.execute(
      "INSERT INTO categories (id, name, type, sort_order) VALUES ($1, $2, $3, $4)",
      [id, name, type, sort]
    );
  }

  // Re-seed child categories (level 2)
  // Note: Assurances (31) is now a non-inputable intermediate parent with level-3 children
  const children: Array<[number, string, number, string, string, number, boolean]> = [
    [10, "Paie", 1, "income", "#22c55e", 1, true],
    [11, "Autres revenus", 1, "income", "#4ade80", 2, true],
    [20, "Loyer", 2, "expense", "#ef4444", 1, true],
    [21, "Électricité", 2, "expense", "#f59e0b", 2, true],
    [22, "Épicerie", 2, "expense", "#10b981", 3, true],
    [23, "Dons", 2, "expense", "#ec4899", 4, true],
    [24, "Restaurant", 2, "expense", "#f97316", 5, true],
    [25, "Frais bancaires", 2, "expense", "#6b7280", 6, true],
    [26, "Jeux, Films & Livres", 2, "expense", "#8b5cf6", 7, true],
    [27, "Abonnements Musique", 2, "expense", "#06b6d4", 8, true],
    [28, "Transport en commun", 2, "expense", "#3b82f6", 9, true],
    [29, "Internet & Télécom", 2, "expense", "#6366f1", 10, true],
    [30, "Animaux", 2, "expense", "#a855f7", 11, true],
    [31, "Assurances", 2, "expense", "#14b8a6", 12, false],  // intermediate parent
    [32, "Pharmacie", 2, "expense", "#f43f5e", 13, true],
    [33, "Taxes municipales", 2, "expense", "#78716c", 14, true],
    [40, "Voiture", 3, "expense", "#64748b", 1, true],
    [41, "Amazon", 3, "expense", "#f59e0b", 2, true],
    [42, "Électroniques", 3, "expense", "#3b82f6", 3, true],
    [43, "Alcool", 3, "expense", "#7c3aed", 4, true],
    [44, "Cadeaux", 3, "expense", "#ec4899", 5, true],
    [45, "Vêtements", 3, "expense", "#d946ef", 6, true],
    [46, "CPA", 3, "expense", "#0ea5e9", 7, true],
    [47, "Voyage", 3, "expense", "#f97316", 8, true],
    [48, "Sports & Plein air", 3, "expense", "#22c55e", 9, true],
    [49, "Spectacles & sorties", 3, "expense", "#e11d48", 10, true],
    [50, "Hypothèque", 4, "expense", "#dc2626", 1, true],
    [51, "Achats maison", 4, "expense", "#ea580c", 2, true],
    [52, "Entretien maison", 4, "expense", "#ca8a04", 3, true],
    [53, "Électroménagers & Meubles", 4, "expense", "#0d9488", 4, true],
    [54, "Outils", 4, "expense", "#b45309", 5, true],
    [60, "Placements", 5, "transfer", "#2563eb", 1, true],
    [61, "Transferts", 5, "transfer", "#7c3aed", 2, true],
    [70, "Impôts", 6, "expense", "#dc2626", 1, true],
    [71, "Paiement CC", 6, "transfer", "#6b7280", 2, true],
    [72, "Retrait cash", 6, "expense", "#57534e", 3, true],
    [73, "Projets", 6, "expense", "#0ea5e9", 4, true],
  ];
  for (const [id, name, parentId, type, color, sort, inputable] of children) {
    await db.execute(
      "INSERT INTO categories (id, name, parent_id, type, color, sort_order, is_inputable) VALUES ($1, $2, $3, $4, $5, $6, $7)",
      [id, name, parentId, type, color, sort, inputable ? 1 : 0]
    );
  }

  // Re-seed grandchild categories (level 3) — under Assurances (31)
  const grandchildren: Array<[number, string, number, string, string, number]> = [
    [310, "Assurance-auto", 31, "expense", "#14b8a6", 1],
    [311, "Assurance-habitation", 31, "expense", "#0d9488", 2],
    [312, "Assurance-vie", 31, "expense", "#0f766e", 3],
  ];
  for (const [id, name, parentId, type, color, sort] of grandchildren) {
    await db.execute(
      "INSERT INTO categories (id, name, parent_id, type, color, sort_order) VALUES ($1, $2, $3, $4, $5, $6)",
      [id, name, parentId, type, color, sort]
    );
  }

  // Re-seed keywords
  const keywords: Array<[string, number]> = [
    ["PAY/PAY", 10],
    ["HYDRO-QUEBEC", 21],
    ["METRO", 22], ["IGA", 22], ["MAXI", 22], ["SUPER C", 22],
    ["BOUCHERIE LAFLECHE", 22], ["BOULANGERIE JARRY", 22], ["DOLLARAMA", 22], ["WALMART", 22],
    ["OXFAM", 23], ["CENTRAIDE", 23], ["FPA", 23],
    ["SUBWAY", 24], ["MCDONALD", 24], ["A&W", 24], ["DD/DOORDASH", 24],
    ["DOORDASH", 24], ["SUSHI", 24], ["DOMINOS", 24], ["BELLE PROVINCE", 24],
    ["PROGRAMME PERFORMANCE", 25],
    ["STEAMGAMES", 26], ["PLAYSTATION", 26], ["PRIMEVIDEO", 26], ["NINTENDO", 26],
    ["RENAUD-BRAY", 26], ["CINEMA DU PARC", 26], ["LEGO", 26],
    ["SPOTIFY", 27],
    ["STM", 28], ["GARE MONT-SAINT", 28], ["GARE SAINT-HUBERT", 28],
    ["GARE CENTRALE", 28], ["REM", 28],
    ["VIDEOTRON", 29], ["ORICOM", 29],
    ["MONDOU", 30],
    ["BELAIR", 310], ["PRYSM", 311], ["INS/ASS", 312],
    ["JEAN COUTU", 32], ["FAMILIPRIX", 32], ["PHARMAPRIX", 32],
    ["M-ST-HILAIRE TX", 33], ["CSS PATRIOT", 33],
    ["SHELL", 40], ["ESSO", 40], ["ULTRAMAR", 40], ["PETRO-CANADA", 40],
    ["SAAQ", 40], ["CREVIER", 40],
    ["AMAZON", 41], ["AMZN", 41],
    ["MICROSOFT", 42], ["ADDISON ELECTRONIQUE", 42],
    ["SAQ", 43], ["SQDC", 43],
    ["DANS UN JARDIN", 44],
    ["UNIQLO", 45], ["WINNERS", 45], ["SIMONS", 45],
    ["ORDRE DES COMPTABL", 46],
    ["NORWEGIAN CRUISE", 47], ["AEROPORTS DE MONTREAL", 47], ["HILTON", 47],
    ["BLOC SHOP", 48], ["SEPAQ", 48], ["LA CORDEE", 48],
    ["MOUNTAIN EQUIPMENT", 48], ["PHYSIOACTIF", 48], ["DECATHLON", 48],
    ["TICKETMASTER", 49], ["CLUB SODA", 49], ["LEPOINTDEVENTE", 49],
    ["MTG/HYP", 50],
    ["CANADIAN TIRE", 51], ["CANAC", 51], ["RONA", 51],
    ["IKEA", 52],
    ["TANGUAY", 53], ["BOUCLAIR", 53],
    ["BMR", 54], ["HOME DEPOT", 54], ["PRINCESS AUTO", 54],
    ["DYNAMIC FUND", 60], ["FIDELITY", 60], ["AGF", 60],
    ["WS INVESTMENTS", 61], ["PEAK INVESTMENT", 61],
    ["GOUV. QUEBEC", 70],
    ["CLAUDE.AI", 73], ["NAME-CHEAP", 73],
  ];
  for (const [kw, catId] of keywords) {
    await db.execute(
      "INSERT INTO keywords (keyword, category_id) VALUES ($1, $2)",
      [kw, catId]
    );
  }
}

export interface KeywordWithCategory {
  id: number;
  keyword: string;
  priority: number;
  category_id: number;
  category_name: string;
  category_color: string;
}

export async function getAllKeywordsWithCategory(): Promise<KeywordWithCategory[]> {
  const db = await getDb();
  return db.select<KeywordWithCategory[]>(
    `SELECT k.id, k.keyword, k.priority, k.category_id,
            c.name AS category_name, c.color AS category_color
     FROM keywords k
     JOIN categories c ON k.category_id = c.id AND c.is_active = 1
     WHERE k.is_active = 1
     ORDER BY k.keyword COLLATE NOCASE`
  );
}

export async function getKeywordsByCategoryId(
  categoryId: number
): Promise<Keyword[]> {
  const db = await getDb();
  return db.select<Keyword[]>(
    `SELECT * FROM keywords WHERE category_id = $1 AND is_active = 1 ORDER BY priority DESC, keyword`,
    [categoryId]
  );
}

export async function createKeyword(
  categoryId: number,
  keyword: string,
  priority: number
): Promise<number> {
  const db = await getDb();
  const result = await db.execute(
    `INSERT INTO keywords (keyword, category_id, priority) VALUES ($1, $2, $3)`,
    [keyword, categoryId, priority]
  );
  return result.lastInsertId as number;
}

export async function updateKeyword(
  id: number,
  keyword: string,
  priority: number
): Promise<void> {
  const db = await getDb();
  await db.execute(
    `UPDATE keywords SET keyword = $1, priority = $2 WHERE id = $3`,
    [keyword, priority, id]
  );
}

export async function deactivateKeyword(id: number): Promise<void> {
  const db = await getDb();
  await db.execute(`UPDATE keywords SET is_active = 0 WHERE id = $1`, [id]);
}
