"use server";

import { revalidatePath } from "next/cache";
import { AppwriteException, ID, Permission, Query, Role } from "node-appwrite";
import { z } from "zod";

import { logAudit } from "./audit";
import {
  DATABASE_ID,
  TABLES,
  type Invoice,
  type InvoiceItem,
  type TenantSettings,
} from "./schema";
import { createAdminClient } from "./server";
import { requireTenant } from "./tenant-guard";
import type { InvoiceActionState } from "./invoice-types";
import { invoiceItemSchema, invoiceSchema } from "@/lib/validation/invoices";

function appwriteError(e: unknown): string {
  if (e instanceof AppwriteException) return e.message || "Beklenmeyen hata.";
  return "Bağlantı hatası. Tekrar deneyin.";
}

function flattenErrors(err: z.ZodError): Record<string, string> {
  const out: Record<string, string> = {};
  for (const issue of err.issues) {
    const key = issue.path.join(".");
    if (key && !out[key]) out[key] = issue.message;
  }
  return out;
}

function teamRowPermissions(tenantId: string) {
  return [
    Permission.read(Role.team(tenantId)),
    Permission.update(Role.team(tenantId)),
    Permission.delete(Role.team(tenantId, "owner")),
    Permission.delete(Role.team(tenantId, "admin")),
  ];
}

function toIso(v: string): string {
  if (!v) return v;
  if (/^\d{4}-\d{2}-\d{2}$/.test(v)) return `${v}T00:00:00.000+00:00`;
  return v;
}

async function nextInvoiceNumber(
  tenantId: string,
): Promise<{ number: string; settingsId: string | null }> {
  const { tablesDB } = createAdminClient();
  const result = await tablesDB.listRows({
    databaseId: DATABASE_ID,
    tableId: TABLES.tenantSettings,
    queries: [Query.equal("tenantId", tenantId), Query.limit(1)],
  });
  const settings = result.rows[0] as unknown as TenantSettings | undefined;
  const prefix = settings?.invoicePrefix || "INV";
  const counter = (settings?.invoiceCounter ?? 0) + 1;
  const year = new Date().getFullYear();
  const number = `${prefix}-${year}-${String(counter).padStart(4, "0")}`;

  if (settings) {
    await tablesDB.updateRow(DATABASE_ID, TABLES.tenantSettings, settings.$id, {
      invoiceCounter: counter,
    });
    return { number, settingsId: settings.$id };
  }
  return { number, settingsId: null };
}

/**
 * Reflect invoice payment status into finance_entries.
 * - status === "paid": ensure exactly one income entry exists for this invoice.
 * - otherwise: remove any income entries linked to this invoice (auto-generated).
 *
 * Best-effort. Failures here must not break the invoice mutation; we log the
 * error to audit but do not throw.
 */
async function syncPaymentEntry(
  tenantId: string,
  userId: string,
  invoice: Invoice,
): Promise<void> {
  try {
    const { tablesDB } = createAdminClient();
    const linked = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.financeEntries,
      queries: [
        Query.equal("tenantId", tenantId),
        Query.equal("invoiceId", invoice.$id),
        Query.equal("type", "income"),
        Query.limit(10),
      ],
    });

    if (invoice.status === "paid") {
      if (linked.rows.length === 0) {
        const row = await tablesDB.createRow(
          DATABASE_ID,
          TABLES.financeEntries,
          ID.unique(),
          {
            tenantId,
            createdBy: userId,
            type: "income",
            amount: Number((invoice.total ?? 0).toFixed(2)),
            date: new Date().toISOString(),
            description: `Fatura ${invoice.number} tahsilatı`,
            customerId: invoice.customerId,
            invoiceId: invoice.$id,
          },
          [
            Permission.read(Role.team(tenantId)),
            Permission.update(Role.team(tenantId)),
            Permission.delete(Role.team(tenantId, "owner")),
            Permission.delete(Role.team(tenantId, "admin")),
          ],
        );
        await logAudit({
          tenantId,
          userId,
          action: "create",
          entityType: "finance_entry",
          entityId: row.$id,
          changes: { auto: "invoice_paid", invoiceId: invoice.$id, amount: invoice.total },
        });
      } else {
        // Keep the first; resync amount in case the invoice total changed
        const first = linked.rows[0];
        const desiredAmount = Number((invoice.total ?? 0).toFixed(2));
        if (
          (first as unknown as { amount: number }).amount !== desiredAmount
        ) {
          await tablesDB.updateRow(DATABASE_ID, TABLES.financeEntries, first.$id, {
            amount: desiredAmount,
          });
        }
      }
    } else {
      for (const r of linked.rows) {
        await tablesDB.deleteRow(DATABASE_ID, TABLES.financeEntries, r.$id);
        await logAudit({
          tenantId,
          userId,
          action: "delete",
          entityType: "finance_entry",
          entityId: r.$id,
          changes: { auto: "invoice_unpaid", invoiceId: invoice.$id },
        });
      }
    }
  } catch {
    // best-effort; don't block the invoice update
  }
}

async function recomputeTotals(
  tenantId: string,
  invoiceId: string,
  userId?: string,
): Promise<{ subtotal: number; vatTotal: number; total: number }> {
  const { tablesDB } = createAdminClient();
  const result = await tablesDB.listRows({
    databaseId: DATABASE_ID,
    tableId: TABLES.invoiceItems,
    queries: [
      Query.equal("tenantId", tenantId),
      Query.equal("invoiceId", invoiceId),
      Query.limit(500),
    ],
  });
  let subtotal = 0;
  let vatTotal = 0;
  for (const r of result.rows as unknown as InvoiceItem[]) {
    const lineNet = (r.quantity ?? 0) * (r.unitPrice ?? 0);
    const lineVat = lineNet * ((r.vatRate ?? 0) / 100);
    subtotal += lineNet;
    vatTotal += lineVat;
  }
  const total = subtotal + vatTotal;
  await tablesDB.updateRow(DATABASE_ID, TABLES.invoices, invoiceId, {
    subtotal: Number(subtotal.toFixed(2)),
    vatTotal: Number(vatTotal.toFixed(2)),
    total: Number(total.toFixed(2)),
  });

  // If invoice is paid, keep the linked income entry's amount in sync.
  if (userId) {
    try {
      const refreshed = (await tablesDB.getRow(
        DATABASE_ID,
        TABLES.invoices,
        invoiceId,
      )) as unknown as Invoice;
      if (refreshed.status === "paid") {
        await syncPaymentEntry(tenantId, userId, refreshed);
      }
    } catch {
      /* best-effort */
    }
  }

  return { subtotal, vatTotal, total };
}

// -------------------- Invoice header --------------------

function pickInvoiceFields(formData: FormData) {
  return {
    customerId: String(formData.get("customerId") ?? ""),
    issueDate: String(formData.get("issueDate") ?? ""),
    dueDate: String(formData.get("dueDate") ?? ""),
    status:
      (formData.get("status") as "draft" | "sent" | "paid" | "overdue" | "cancelled" | null) ??
      "draft",
    notes: String(formData.get("notes") ?? "").trim(),
  };
}

export async function createInvoiceAction(
  _prev: InvoiceActionState,
  formData: FormData,
): Promise<InvoiceActionState> {
  let ctx;
  try {
    ctx = await requireTenant();
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }

  const parsed = invoiceSchema.safeParse(pickInvoiceFields(formData));
  if (!parsed.success) {
    return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) };
  }

  let invoiceId = "";
  try {
    const { tablesDB } = createAdminClient();
    const { number } = await nextInvoiceNumber(ctx.tenantId);

    const data = {
      ...parsed.data,
      issueDate: toIso(parsed.data.issueDate),
      dueDate: toIso(parsed.data.dueDate),
      number,
      subtotal: 0,
      vatTotal: 0,
      total: 0,
    };

    const row = await tablesDB.createRow(
      DATABASE_ID,
      TABLES.invoices,
      ID.unique(),
      {
        tenantId: ctx.tenantId,
        createdBy: ctx.user.id,
        ...data,
      },
      teamRowPermissions(ctx.tenantId),
    );
    invoiceId = row.$id;

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "invoice",
      entityId: row.$id,
      changes: { number, customerId: parsed.data.customerId },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

  revalidatePath("/invoices");
  return { ok: true, invoiceId };
}

export async function updateInvoiceAction(
  _prev: InvoiceActionState,
  formData: FormData,
): Promise<InvoiceActionState> {
  const id = String(formData.get("id") ?? "");
  if (!id) return { ok: false, error: "ID eksik." };

  let ctx;
  try {
    ctx = await requireTenant();
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }

  const parsed = invoiceSchema.safeParse(pickInvoiceFields(formData));
  if (!parsed.success) {
    return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) };
  }

  try {
    const { tablesDB } = createAdminClient();
    const existing = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoices,
      id,
    )) as unknown as Invoice;
    if (existing.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }

    const data = {
      ...parsed.data,
      issueDate: toIso(parsed.data.issueDate),
      dueDate: toIso(parsed.data.dueDate),
    };
    await tablesDB.updateRow(DATABASE_ID, TABLES.invoices, id, data);

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "invoice",
      entityId: id,
      changes: data,
    });

    // Sync payment ↔ finance entry on every save (cheap; idempotent).
    const refreshed = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoices,
      id,
    )) as unknown as Invoice;
    await syncPaymentEntry(ctx.tenantId, ctx.user.id, refreshed);
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

  revalidatePath("/invoices");
  revalidatePath(`/invoices/${id}`);
  revalidatePath("/finance");
  return { ok: true, invoiceId: id };
}

export async function deleteInvoiceAction(
  formData: FormData,
): Promise<InvoiceActionState> {
  const id = String(formData.get("id") ?? "");
  if (!id) return { ok: false, error: "ID eksik." };

  let ctx;
  try {
    ctx = await requireTenant();
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }

  try {
    const { tablesDB } = createAdminClient();
    const existing = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoices,
      id,
    )) as unknown as Invoice;
    if (existing.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }

    // Delete items first
    const items = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.invoiceItems,
      queries: [
        Query.equal("tenantId", ctx.tenantId),
        Query.equal("invoiceId", id),
        Query.limit(500),
      ],
    });
    for (const it of items.rows) {
      await tablesDB.deleteRow(DATABASE_ID, TABLES.invoiceItems, it.$id);
    }

    // Cascade-delete any auto-generated income entries linked to this invoice
    const linkedEntries = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.financeEntries,
      queries: [
        Query.equal("tenantId", ctx.tenantId),
        Query.equal("invoiceId", id),
        Query.limit(50),
      ],
    });
    for (const r of linkedEntries.rows) {
      await tablesDB.deleteRow(DATABASE_ID, TABLES.financeEntries, r.$id);
    }

    await tablesDB.deleteRow(DATABASE_ID, TABLES.invoices, id);

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "delete",
      entityType: "invoice",
      entityId: id,
      changes: {
        number: existing.number,
        items: items.rows.length,
        linkedFinanceEntries: linkedEntries.rows.length,
      },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

  revalidatePath("/invoices");
  revalidatePath("/finance");
  return { ok: true };
}

// -------------------- Invoice items --------------------

function pickItemFields(formData: FormData) {
  return {
    description: String(formData.get("description") ?? "").trim(),
    quantity: String(formData.get("quantity") ?? "1"),
    unitPrice: String(formData.get("unitPrice") ?? "0"),
    vatRate: String(formData.get("vatRate") ?? "20"),
  };
}

function lineTotal(qty: number, unitPrice: number, vatRate: number): number {
  const net = qty * unitPrice;
  const vat = net * (vatRate / 100);
  return Number((net + vat).toFixed(2));
}

export async function addInvoiceItemAction(
  _prev: InvoiceActionState,
  formData: FormData,
): Promise<InvoiceActionState> {
  const invoiceId = String(formData.get("invoiceId") ?? "");
  if (!invoiceId) return { ok: false, error: "Fatura ID eksik." };

  let ctx;
  try {
    ctx = await requireTenant();
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }

  const parsed = invoiceItemSchema.safeParse(pickItemFields(formData));
  if (!parsed.success) {
    return { ok: false, error: "Kalem geçersiz.", fieldErrors: flattenErrors(parsed.error) };
  }

  try {
    const { tablesDB } = createAdminClient();
    const invoice = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoices,
      invoiceId,
    )) as unknown as Invoice;
    if (invoice.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }

    const total = lineTotal(parsed.data.quantity, parsed.data.unitPrice, parsed.data.vatRate ?? 0);

    const row = await tablesDB.createRow(
      DATABASE_ID,
      TABLES.invoiceItems,
      ID.unique(),
      {
        tenantId: ctx.tenantId,
        createdBy: ctx.user.id,
        invoiceId,
        description: parsed.data.description,
        quantity: parsed.data.quantity,
        unitPrice: parsed.data.unitPrice,
        vatRate: parsed.data.vatRate ?? 0,
        lineTotal: total,
      },
      teamRowPermissions(ctx.tenantId),
    );

    await recomputeTotals(ctx.tenantId, invoiceId, ctx.user.id);

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "invoice_item",
      entityId: row.$id,
      changes: { invoiceId, ...parsed.data, lineTotal: total },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

  revalidatePath(`/invoices/${invoiceId}`);
  return { ok: true, invoiceId };
}

export async function updateInvoiceItemAction(
  _prev: InvoiceActionState,
  formData: FormData,
): Promise<InvoiceActionState> {
  const id = String(formData.get("id") ?? "");
  if (!id) return { ok: false, error: "ID eksik." };

  let ctx;
  try {
    ctx = await requireTenant();
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }

  const parsed = invoiceItemSchema.safeParse(pickItemFields(formData));
  if (!parsed.success) {
    return { ok: false, error: "Kalem geçersiz.", fieldErrors: flattenErrors(parsed.error) };
  }

  try {
    const { tablesDB } = createAdminClient();
    const existing = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoiceItems,
      id,
    )) as unknown as InvoiceItem;
    if (existing.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }

    const total = lineTotal(parsed.data.quantity, parsed.data.unitPrice, parsed.data.vatRate ?? 0);

    await tablesDB.updateRow(DATABASE_ID, TABLES.invoiceItems, id, {
      description: parsed.data.description,
      quantity: parsed.data.quantity,
      unitPrice: parsed.data.unitPrice,
      vatRate: parsed.data.vatRate ?? 0,
      lineTotal: total,
    });

    await recomputeTotals(ctx.tenantId, existing.invoiceId, ctx.user.id);

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "invoice_item",
      entityId: id,
      changes: { ...parsed.data, lineTotal: total },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

  revalidatePath(`/invoices/${(await getItemInvoiceId(id)) ?? ""}`);
  return { ok: true };
}

async function getItemInvoiceId(itemId: string): Promise<string | null> {
  try {
    const { tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoiceItems,
      itemId,
    )) as unknown as InvoiceItem;
    return row.invoiceId;
  } catch {
    return null;
  }
}

export async function deleteInvoiceItemAction(
  formData: FormData,
): Promise<InvoiceActionState> {
  const id = String(formData.get("id") ?? "");
  if (!id) return { ok: false, error: "ID eksik." };

  let ctx;
  try {
    ctx = await requireTenant();
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }

  try {
    const { tablesDB } = createAdminClient();
    const existing = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.invoiceItems,
      id,
    )) as unknown as InvoiceItem;
    if (existing.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }

    await tablesDB.deleteRow(DATABASE_ID, TABLES.invoiceItems, id);
    await recomputeTotals(ctx.tenantId, existing.invoiceId, ctx.user.id);

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "delete",
      entityType: "invoice_item",
      entityId: id,
      changes: { invoiceId: existing.invoiceId },
    });

    revalidatePath(`/invoices/${existing.invoiceId}`);
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

  return { ok: true };
}
