import type Anthropic from "@anthropic-ai/sdk";
import prisma from "../config/database";
import { listInvoices, getInvoiceStats } from "./invoices.service";
import { listOffers } from "./offers.service";
import { listOrders } from "./orders.service";
import { listIssuedOrders } from "./issued-orders.service";
import { listProjects } from "./projects.service";
import { getBelowMinimumItems } from "./warehouse.service";

/**
 * Odin Phase 2a — READ-ONLY tools over the existing service layer.
 *
 * Security model (see docs/superpowers/specs/2026-06-08-odin-phase2-…):
 * - The assistant is the USER'S DELEGATE: every handler re-checks the same
 *   permission the equivalent route requires, against the caller's authData.
 *   The tool list sent to the model is ALSO pre-filtered by permission, but
 *   the handler check is the actual boundary (defense in depth).
 * - No write tools exist in this layer at all — a prompt-injected "create an
 *   order" cannot do anything, because there is no tool to call.
 * - Handlers call the SAME service functions the routes use (or replicate a
 *   route's plain read), so ownership/visibility logic cannot diverge.
 * - Outputs are deliberately compact (capped rows, plain fields): tool
 *   results are model input and bill as tokens on every loop iteration.
 */

export interface AiAuthCtx {
  userId: number;
  roleName: string;
  permissions: string[];
}

export function ctxCan(ctx: AiAuthCtx, permission: string): boolean {
  return ctx.roleName === "admin" || ctx.permissions.includes(permission);
}

/** Uniform list paging the tools use — first page, newest first, small. */
const LIST = { page: 1, limit: 20, skip: 0, order: "desc" as const };
const DENIED = (what: string) =>
  `Uživatel nemá oprávnění zobrazit ${what} — odpověz mu, že na to nemá oprávnění.`;

interface ToolSpec {
  /** Permission whose holder may use this tool (admin bypasses). */
  permission: string;
  definition: Anthropic.Tool;
  handler: (input: Record<string, unknown>, ctx: AiAuthCtx) => Promise<unknown>;
}

const num = (v: unknown): number | undefined => {
  const n = Number(v);
  return Number.isFinite(n) && n > 0 ? n : undefined;
};
const str = (v: unknown): string | undefined =>
  typeof v === "string" && v.trim() ? v.trim() : undefined;

const TOOLS: ToolSpec[] = [
  {
    permission: "invoices.view",
    definition: {
      name: "list_invoices",
      description:
        "Vydané faktury (faktury, které firma vystavila zákazníkům). Vrací max 20 nejnovějších odpovídajících faktur s částkami včetně DPH. Volej při dotazech na fakturace, tržby konkrétních faktur, splatnosti, neuhrazené faktury.",
      input_schema: {
        type: "object",
        properties: {
          status: {
            type: "string",
            enum: ["draft", "issued", "overdue", "paid"],
            description: "Filtr stavu (vynech pro všechny)",
          },
          month: { type: "number", description: "Měsíc vystavení 1-12" },
          year: { type: "number", description: "Rok vystavení" },
          search: {
            type: "string",
            description: "Hledání podle čísla faktury nebo zákazníka",
          },
        },
      },
    },
    handler: async (input) => {
      const res = await listInvoices({
        ...LIST,
        sort: "id",
        search: str(input.search) ?? "",
        status: str(input.status),
        month: num(input.month),
        year: num(input.year),
      });
      return {
        total_matching: res.total,
        shown: res.data.length,
        invoices: res.data.map((i) => ({
          number: i.invoice_number,
          customer: i.customer_name,
          status: i.status,
          issue_date: i.issue_date,
          due_date: i.due_date,
          total_with_vat: i.total,
          currency: i.currency,
        })),
      };
    },
  },
  {
    permission: "invoices.view",
    definition: {
      name: "get_invoice_stats",
      description:
        "Měsíční přehled vydaných faktur: zaplaceno tento měsíc, čekající a po splatnosti (vč. přepočtu do CZK) a DPH za měsíc. Volej při dotazech typu 'kolik jsme letos/tento měsíc fakturovali', 'kolik nám dluží'.",
      input_schema: {
        type: "object",
        properties: {
          month: {
            type: "number",
            description: "Měsíc 1-12 (výchozí aktuální)",
          },
          year: { type: "number", description: "Rok (výchozí aktuální)" },
        },
      },
    },
    handler: async (input) =>
      getInvoiceStats(num(input.month), num(input.year)),
  },
  {
    permission: "invoices.view",
    definition: {
      name: "list_received_invoices",
      description:
        "Přijaté faktury (faktury od dodavatelů, výdaje firmy). Vrací max 20 nejnovějších; částky jsou VČETNĚ DPH. Volej při dotazech na výdaje, faktury od dodavatele, co je k úhradě.",
      input_schema: {
        type: "object",
        properties: {
          month: { type: "number", description: "Měsíc vystavení 1-12" },
          year: { type: "number", description: "Rok vystavení" },
          search: {
            type: "string",
            description: "Hledání podle dodavatele nebo čísla faktury",
          },
        },
      },
    },
    handler: async (input) => {
      // Mirrors the received-invoices route's plain read (no dedicated list
      // service exists); read-only, same visibility (no per-user scoping).
      const where: Record<string, unknown> = {};
      const month = num(input.month);
      const year = num(input.year);
      if (month && year) {
        where.issue_date = {
          gte: new Date(Date.UTC(year, month - 1, 1)),
          lt: new Date(Date.UTC(year, month, 1)),
        };
      }
      const search = str(input.search);
      if (search) {
        where.OR = [
          { supplier_name: { contains: search } },
          { invoice_number: { contains: search } },
        ];
      }
      const [rows, total] = await Promise.all([
        prisma.received_invoices.findMany({
          where,
          orderBy: [{ issue_date: "desc" }, { id: "desc" }],
          take: 20,
          select: {
            supplier_name: true,
            invoice_number: true,
            amount: true,
            currency: true,
            issue_date: true,
            due_date: true,
            status: true,
          },
        }),
        prisma.received_invoices.count({ where }),
      ]);
      return {
        total_matching: total,
        shown: rows.length,
        note: "Částky jsou včetně DPH (gross).",
        invoices: rows.map((r) => ({
          supplier: r.supplier_name,
          number: r.invoice_number,
          amount_with_vat: Number(r.amount),
          currency: r.currency,
          issue_date: r.issue_date,
          due_date: r.due_date,
          status: r.status,
        })),
      };
    },
  },
  {
    permission: "offers.view",
    definition: {
      name: "list_offers",
      description:
        "Nabídky (cenové nabídky zákazníkům). Vrací max 20 nejnovějších; ceny jsou BEZ DPH (nabídka není daňový doklad). Stavy: draft (koncept), active (aktivní), ordered (objednaná zákazníkem), invalidated (zneplatněná).",
      input_schema: {
        type: "object",
        properties: {
          status: {
            type: "string",
            enum: ["draft", "active", "ordered", "invalidated"],
          },
          search: {
            type: "string",
            description: "Hledání podle čísla, projektu nebo zákazníka",
          },
        },
      },
    },
    handler: async (input) => {
      const res = await listOffers({
        ...LIST,
        sort: "id",
        search: str(input.search) ?? "",
        status: str(input.status),
      });
      return {
        total_matching: res.total,
        shown: res.data.length,
        note: "Ceny jsou bez DPH.",
        offers: res.data.map((o) => ({
          number: o.quotation_number,
          customer: o.customer_name,
          status: o.status,
          project_code: o.project_code,
          valid_until: o.valid_until,
          total_excl_vat: o.total,
          currency: o.currency,
        })),
      };
    },
  },
  {
    permission: "orders.view",
    definition: {
      name: "list_orders",
      description:
        "Přijaté objednávky (objednávky od zákazníků). Vrací max 20 nejnovějších; ceny BEZ DPH.",
      input_schema: {
        type: "object",
        properties: {
          search: {
            type: "string",
            description: "Hledání podle čísla objednávky nebo zákazníka",
          },
          month: { type: "number", description: "Měsíc vytvoření 1-12" },
          year: { type: "number", description: "Rok vytvoření" },
        },
      },
    },
    handler: async (input) => {
      const res = await listOrders({
        ...LIST,
        sort: "id",
        search: str(input.search) ?? "",
        month: num(input.month),
        year: num(input.year),
      });
      return {
        total_matching: res.total,
        shown: res.data.length,
        note: "Ceny jsou bez DPH.",
        orders: res.data.map((o) => ({
          number: o.order_number,
          customer_order_number: o.customer_order_number,
          customer: o.customer_name,
          status: o.status,
          created_at: o.created_at,
          total_excl_vat: o.total,
          currency: o.currency,
        })),
      };
    },
  },
  {
    permission: "orders.view",
    definition: {
      name: "list_issued_orders",
      description:
        "Vydané objednávky (objednávky firmy u dodavatelů, nákupy). Vrací max 20 nejnovějších; ceny BEZ DPH. Stavy: draft (koncept), sent (odeslaná), confirmed (potvrzená), completed (dokončená), cancelled (zrušená).",
      input_schema: {
        type: "object",
        properties: {
          status: { type: "string" },
          search: {
            type: "string",
            description: "Hledání podle čísla nebo dodavatele",
          },
        },
      },
    },
    handler: async (input) => {
      const res = await listIssuedOrders({
        ...LIST,
        sort: "id",
        search: str(input.search) ?? "",
        status: str(input.status),
      });
      return {
        total_matching: res.total,
        shown: res.data.length,
        note: "Ceny jsou bez DPH.",
        issued_orders: res.data.map((o) => ({
          number: o.po_number,
          supplier: o.supplier_name,
          status: o.status,
          order_date: o.order_date,
          total_excl_vat: o.total,
          currency: o.currency,
        })),
      };
    },
  },
  {
    permission: "projects.view",
    definition: {
      name: "list_projects",
      description:
        "Projekty firmy. Vrací max 20 nejnovějších. Stavy: aktivni, dokonceny, zruseny.",
      input_schema: {
        type: "object",
        properties: {
          status: { type: "string", enum: ["aktivni", "dokonceny", "zruseny"] },
          search: {
            type: "string",
            description: "Hledání podle čísla, názvu nebo zákazníka",
          },
        },
      },
    },
    handler: async (input) => {
      const res = await listProjects({
        ...LIST,
        sort: "id",
        search: str(input.search) ?? "",
        status: str(input.status),
      });
      return {
        total_matching: res.total,
        shown: res.data.length,
        projects: res.data.map((p) => ({
          number: p.project_number,
          name: p.name,
          customer: p.customer_name,
          responsible: p.responsible_user_name,
          status: p.status,
          start_date: p.start_date,
          end_date: p.end_date,
        })),
      };
    },
  },
  {
    permission: "customers.view",
    definition: {
      name: "list_customers",
      description:
        "Zákazníci firmy (adresy, IČO, DIČ). Vrací max 20 odpovídajících.",
      input_schema: {
        type: "object",
        properties: {
          search: {
            type: "string",
            description: "Hledání podle názvu nebo IČO",
          },
        },
      },
    },
    handler: async (input) => {
      // Mirrors the customers route's read (search on name/company_id).
      const search = str(input.search);
      const where = search
        ? {
            OR: [
              { name: { contains: search } },
              { company_id: { contains: search } },
            ],
          }
        : {};
      const [rows, total] = await Promise.all([
        prisma.customers.findMany({
          where,
          orderBy: { name: "asc" },
          take: 20,
          select: {
            name: true,
            city: true,
            company_id: true,
            vat_id: true,
          },
        }),
        prisma.customers.count({ where }),
      ]);
      return {
        total_matching: total,
        shown: rows.length,
        customers: rows.map((c) => ({
          name: c.name,
          city: c.city,
          ico: c.company_id,
          dic: c.vat_id,
        })),
      };
    },
  },
  {
    permission: "warehouse.view",
    definition: {
      name: "get_stock_overview",
      description:
        "Sklad: položky pod minimální zásobou, nebo vyhledání skladové položky s aktuální dostupností. Volej při dotazech na sklad, zásoby, co dochází.",
      input_schema: {
        type: "object",
        properties: {
          search: {
            type: "string",
            description:
              "Hledání položky podle názvu nebo kódu (vynech pro přehled položek pod minimem)",
          },
        },
      },
    },
    handler: async (input) => {
      const search = str(input.search);
      if (!search) {
        const below = await getBelowMinimumItems();
        return {
          below_minimum_count: below.length,
          items: below.slice(0, 20).map((i) => ({
            item_number: i.item_number,
            name: i.name,
            current_stock: i.current_stock,
            minimum: i.min_quantity,
            unit: i.unit,
          })),
        };
      }
      // Stock math mirrors the service's total-stock rule: sum of
      // unconsumed batch quantities.
      const rows = await prisma.sklad_items.findMany({
        where: {
          is_active: true,
          OR: [
            { name: { contains: search } },
            { item_number: { contains: search } },
          ],
        },
        take: 20,
        select: {
          item_number: true,
          name: true,
          unit: true,
          batches: {
            where: { is_consumed: false },
            select: { quantity: true },
          },
        },
      });
      return {
        shown: rows.length,
        items: rows.map((r) => ({
          item_number: r.item_number,
          name: r.name,
          unit: r.unit,
          on_stock: r.batches.reduce((s, b) => s + Number(b.quantity), 0),
        })),
      };
    },
  },
  {
    // Own attendance is always visible; other users need attendance.manage —
    // enforced inside the handler (the coarse gate is "has attendance.record").
    permission: "attendance.record",
    definition: {
      name: "get_attendance_summary",
      description:
        "Souhrn docházky za měsíc: počty dnů podle typu (práce, dovolená, nemoc…). Bez user_id vrací docházku PŘIHLÁŠENÉHO uživatele; cizí docházka vyžaduje oprávnění správy docházky.",
      input_schema: {
        type: "object",
        properties: {
          month: {
            type: "number",
            description: "Měsíc 1-12 (výchozí aktuální)",
          },
          year: { type: "number", description: "Rok (výchozí aktuální)" },
          user_id: {
            type: "number",
            description: "ID jiného uživatele (jen se správou docházky)",
          },
        },
      },
    },
    handler: async (input, ctx) => {
      const now = new Date();
      const month = num(input.month) ?? now.getMonth() + 1;
      const year = num(input.year) ?? now.getFullYear();
      const targetUserId = num(input.user_id) ?? ctx.userId;
      if (targetUserId !== ctx.userId && !ctxCan(ctx, "attendance.manage")) {
        return { error: DENIED("docházku jiných uživatelů") };
      }
      // shift_date is @db.Date → UTC-midnight month boundaries, half-open.
      const rows = await prisma.attendance.findMany({
        where: {
          user_id: targetUserId,
          shift_date: {
            gte: new Date(Date.UTC(year, month - 1, 1)),
            lt: new Date(Date.UTC(year, month, 1)),
          },
        },
        select: { leave_type: true, shift_date: true },
      });
      const byType: Record<string, number> = {};
      for (const r of rows) {
        const t = r.leave_type ?? "work";
        byType[t] = (byType[t] || 0) + 1;
      }
      return {
        user_id: targetUserId,
        month,
        year,
        days_recorded: rows.length,
        days_by_type: byType,
        note: "Počty dnů se záznamem podle typu; ne odpracované hodiny.",
      };
    },
  },
];

/** Tool definitions the caller may use — filtered by their permissions. */
export function toolDefinitionsFor(ctx: AiAuthCtx): Anthropic.Tool[] {
  return TOOLS.filter((t) => ctxCan(ctx, t.permission)).map(
    (t) => t.definition,
  );
}

/**
 * Execute one tool call AS the user. Returns the JSON-serializable result;
 * permission failures and handler errors come back as `{ error }` results
 * (is_error on the tool_result), never as thrown exceptions — the model
 * should relay them, not crash the loop.
 */
export async function executeTool(
  name: string,
  input: Record<string, unknown>,
  ctx: AiAuthCtx,
): Promise<{ ok: boolean; result: unknown }> {
  const tool = TOOLS.find((t) => t.definition.name === name);
  if (!tool)
    return { ok: false, result: { error: `Neznámý nástroj: ${name}` } };
  // The boundary check: even if the model hallucinated a tool it wasn't
  // given, the user's permissions decide.
  if (!ctxCan(ctx, tool.permission)) {
    return { ok: false, result: { error: DENIED("tato data") } };
  }
  try {
    return { ok: true, result: await tool.handler(input, ctx) };
  } catch (e) {
    console.error(`[ai-tools] ${name} failed`, e);
    return {
      ok: false,
      result: {
        error: "Nástroj selhal — zkus to jinak nebo to uživateli sděl.",
      },
    };
  }
}

/** Czech display labels for the frontend tool-activity chips. */
export const TOOL_LABELS: Record<string, string> = {
  list_invoices: "Faktury vydané",
  get_invoice_stats: "Statistiky fakturace",
  list_received_invoices: "Faktury přijaté",
  list_offers: "Nabídky",
  list_orders: "Objednávky přijaté",
  list_issued_orders: "Objednávky vydané",
  list_projects: "Projekty",
  list_customers: "Zákazníci",
  get_stock_overview: "Sklad",
  get_attendance_summary: "Docházka",
};
