import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";

export interface TransactionRow {
  id: string;
  direction: "in" | "out";
  amount: number;
  currency: string;
  category: string;
  description: string;
  counterparty: string | null;
  method: string;
  reference: string | null;
  occurred_on: string;
  created_at: string;
}

export interface ReceiptItem {
  description: string;
  quantity: number;
  unitPrice: number;
}

export interface ReceiptRow {
  id: string;
  receipt_no: string;
  issued_to: string;
  issued_to_email: string | null;
  items: ReceiptItem[];
  amount: number;
  currency: string;
  method: string;
  notes: string | null;
  issued_on: string;
  created_at: string;
}

export interface AuditRow {
  id: string;
  actor_email: string | null;
  action: string;
  entity: string;
  entity_id: string | null;
  details: Record<string, string | number | boolean | null>;
  created_at: string;
}

export interface FinanceMember {
  user_id: string;
  role: string;
  created_at: string;
}

type Ctx = { supabase: any; userId: string; claims?: { email?: string } };

async function assertFinance(context: Ctx) {
  const { data, error } = await context.supabase.rpc("is_finance_member", {
    _user_id: context.userId,
  });
  if (error) throw new Error("Could not verify your permissions.");
  if (!data) throw new Error("Forbidden: finance access required.");
}

async function assertAdmin(context: Ctx) {
  const { data, error } = await context.supabase.rpc("has_role", {
    _user_id: context.userId,
    _role: "admin",
  });
  if (error) throw new Error("Could not verify your permissions.");
  if (!data) throw new Error("Forbidden: admin access required.");
}

async function audit(
  context: Ctx,
  action: string,
  entity: string,
  entityId: string | null,
  details: Record<string, unknown> = {},
) {
  const { error } = await context.supabase.from("finance_audit_log").insert({
    actor_id: context.userId,
    actor_email: context.claims?.email ?? null,
    action,
    entity,
    entity_id: entityId,
    details,
  });
  if (error) console.error("finance audit log failed:", error.message);
}

export const financeAccess = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const [member, admin] = await Promise.all([
      context.supabase.rpc("is_finance_member", { _user_id: context.userId }),
      context.supabase.rpc("has_role", { _user_id: context.userId, _role: "admin" }),
    ]);
    return {
      canAccess: Boolean(member.data),
      isAdmin: Boolean(admin.data),
      email: (context.claims as { email?: string })?.email ?? null,
    };
  });

export const financeOverview = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    await assertFinance(context);
    const [txRes, rcRes, auditRes, membersRes] = await Promise.all([
      context.supabase
        .from("finance_transactions")
        .select("*")
        .order("occurred_on", { ascending: false })
        .order("created_at", { ascending: false })
        .limit(500),
      context.supabase
        .from("finance_receipts")
        .select("*")
        .order("created_at", { ascending: false })
        .limit(200),
      context.supabase
        .from("finance_audit_log")
        .select("*")
        .order("created_at", { ascending: false })
        .limit(100),
      context.supabase
        .from("user_roles")
        .select("user_id, role, created_at")
        .eq("role", "finance"),
    ]);

    if (txRes.error) throw new Error(txRes.error.message);
    if (rcRes.error) throw new Error(rcRes.error.message);

    const transactions = (txRes.data ?? []).map((t: any) => ({
      ...t,
      amount: Number(t.amount),
    })) as TransactionRow[];

    const moneyIn = transactions
      .filter((t) => t.direction === "in")
      .reduce((s, t) => s + t.amount, 0);
    const moneyOut = transactions
      .filter((t) => t.direction === "out")
      .reduce((s, t) => s + t.amount, 0);

    return {
      transactions,
      receipts: ((rcRes.data ?? []) as any[]).map((r) => ({
        ...r,
        amount: Number(r.amount),
        items: Array.isArray(r.items) ? r.items : [],
      })) as ReceiptRow[],
      audit: (auditRes.data ?? []) as AuditRow[],
      members: (membersRes.data ?? []) as FinanceMember[],
      totals: { moneyIn, moneyOut, balance: moneyIn - moneyOut },
    };
  });

const transactionSchema = z.object({
  direction: z.enum(["in", "out"]),
  amount: z.number().positive().max(1_000_000_000),
  currency: z.string().trim().min(1).max(8).default("UGX"),
  category: z.string().trim().min(1).max(60).default("general"),
  description: z.string().trim().min(2).max(400),
  counterparty: z.string().trim().max(160).optional(),
  method: z.string().trim().min(1).max(40).default("cash"),
  reference: z.string().trim().max(80).optional(),
  occurred_on: z.string().trim().min(8).max(10),
});

export const addTransaction = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: unknown) => transactionSchema.parse(input))
  .handler(async ({ data, context }) => {
    await assertFinance(context);
    const { data: row, error } = await context.supabase
      .from("finance_transactions")
      .insert({ ...data, created_by: context.userId })
      .select("id")
      .single();
    if (error) throw new Error(error.message);
    await audit(context, "create", "transaction", row.id, {
      direction: data.direction,
      amount: data.amount,
      currency: data.currency,
    });
    return { ok: true as const, id: row.id as string };
  });

export const deleteTransaction = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: { id: string }) => z.object({ id: z.string().uuid() }).parse(input))
  .handler(async ({ data, context }) => {
    await assertAdmin(context);
    const { error } = await context.supabase
      .from("finance_transactions")
      .delete()
      .eq("id", data.id);
    if (error) throw new Error(error.message);
    await audit(context, "delete", "transaction", data.id);
    return { ok: true as const };
  });

const receiptSchema = z.object({
  issued_to: z.string().trim().min(2).max(160),
  issued_to_email: z.string().trim().email().max(255).optional().or(z.literal("")),
  items: z
    .array(
      z.object({
        description: z.string().trim().min(1).max(200),
        quantity: z.number().positive().max(100000),
        unitPrice: z.number().nonnegative().max(1_000_000_000),
      }),
    )
    .min(1)
    .max(30),
  currency: z.string().trim().min(1).max(8).default("UGX"),
  method: z.string().trim().min(1).max(40).default("cash"),
  notes: z.string().trim().max(600).optional(),
  recordAsIncome: z.boolean().default(true),
});

export const createReceipt = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: unknown) => receiptSchema.parse(input))
  .handler(async ({ data, context }) => {
    await assertFinance(context);
    const amount = data.items.reduce((s, i) => s + i.quantity * i.unitPrice, 0);

    let transactionId: string | null = null;
    if (data.recordAsIncome && amount > 0) {
      const { data: tx, error: txError } = await context.supabase
        .from("finance_transactions")
        .insert({
          direction: "in",
          amount,
          currency: data.currency,
          category: "receipt",
          description: `Receipt for ${data.issued_to}`,
          counterparty: data.issued_to,
          method: data.method,
          created_by: context.userId,
        })
        .select("id")
        .single();
      if (txError) throw new Error(txError.message);
      transactionId = tx.id as string;
    }

    const { data: row, error } = await context.supabase
      .from("finance_receipts")
      .insert({
        issued_to: data.issued_to,
        issued_to_email: data.issued_to_email || null,
        items: data.items,
        amount,
        currency: data.currency,
        method: data.method,
        notes: data.notes ?? null,
        issued_by: context.userId,
        transaction_id: transactionId,
      })
      .select("*")
      .single();
    if (error) throw new Error(error.message);

    await audit(context, "create", "receipt", row.id, {
      receipt_no: row.receipt_no,
      amount,
      currency: data.currency,
    });

    return { ...row, amount: Number(row.amount), items: data.items } as ReceiptRow;
  });

export const addFinanceUser = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: unknown) =>
    z
      .object({
        email: z.string().trim().email().max(255),
        password: z.string().min(10).max(72),
        makeAdmin: z.boolean().default(false),
      })
      .parse(input),
  )
  .handler(async ({ data, context }) => {
    await assertAdmin(context);
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");

    let userId: string | null = null;
    const created = await supabaseAdmin.auth.admin.createUser({
      email: data.email,
      password: data.password,
      email_confirm: true,
    });

    if (created.error) {
      const { data: list } = await supabaseAdmin.auth.admin.listUsers({ page: 1, perPage: 1000 });
      const existing = list?.users.find(
        (u) => u.email?.toLowerCase() === data.email.toLowerCase(),
      );
      if (!existing) throw new Error(created.error.message);
      userId = existing.id;
      await supabaseAdmin.auth.admin.updateUserById(existing.id, { password: data.password });
    } else {
      userId = created.data.user?.id ?? null;
    }
    if (!userId) throw new Error("Could not create the finance user.");

    const roles = data.makeAdmin ? ["finance", "admin"] : ["finance"];
    const { error: roleError } = await supabaseAdmin
      .from("user_roles")
      .upsert(
        roles.map((role) => ({ user_id: userId as string, role: role as "finance" | "admin" })),
        { onConflict: "user_id,role" },
      );
    if (roleError) throw new Error(roleError.message);

    await audit(context, "create", "finance_user", userId, { email: data.email, roles });
    return { ok: true as const, userId };
  });

export const removeFinanceUser = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((input: { userId: string }) =>
    z.object({ userId: z.string().uuid() }).parse(input),
  )
  .handler(async ({ data, context }) => {
    await assertAdmin(context);
    if (data.userId === context.userId) throw new Error("You cannot remove your own access.");
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const { error } = await supabaseAdmin
      .from("user_roles")
      .delete()
      .eq("user_id", data.userId)
      .eq("role", "finance");
    if (error) throw new Error(error.message);
    await audit(context, "delete", "finance_user", data.userId);
    return { ok: true as const };
  });

export const listFinanceUserEmails = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    await assertFinance(context);
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    const [{ data: roles }, { data: list }] = await Promise.all([
      supabaseAdmin.from("user_roles").select("user_id, role"),
      supabaseAdmin.auth.admin.listUsers({ page: 1, perPage: 1000 }),
    ]);
    const emails = new Map((list?.users ?? []).map((u) => [u.id, u.email ?? ""]));
    const byUser = new Map<string, string[]>();
    for (const r of roles ?? []) {
      byUser.set(r.user_id, [...(byUser.get(r.user_id) ?? []), r.role]);
    }
    return [...byUser.entries()]
      .filter(([, r]) => r.includes("finance") || r.includes("admin"))
      .map(([userId, r]) => ({ userId, email: emails.get(userId) ?? "unknown", roles: r }));
  });
