"use server";

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

import { logAudit } from "./audit";
import {
  DATABASE_ID,
  TABLES,
  type BankLoan,
  type LoanInstallment,
} from "./schema";
import { canAccessRow, scopedRowPermissions } from "./scope-permissions";
import { createAdminClient } from "./server";
import { requireTenant } from "./tenant-guard";
import type { LoanActionState } from "./loan-types";
import { bankLoanSchema } from "@/lib/validation/bank-loans";

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 pickLoanFields(formData: FormData) {
  return {
    bankAccountId: String(formData.get("bankAccountId") ?? ""),
    bankName: String(formData.get("bankName") ?? "").trim(),
    loanName: String(formData.get("loanName") ?? "").trim(),
    loanType: formData.get("loanType") as
      | "consumer"
      | "vehicle"
      | "housing"
      | "commercial"
      | "kmh"
      | "other"
      | null,
    principal: String(formData.get("principal") ?? "0"),
    interestRate: String(formData.get("interestRate") ?? "0"),
    termMonths: String(formData.get("termMonths") ?? "12"),
    startDate: String(formData.get("startDate") ?? ""),
    paymentDay: String(formData.get("paymentDay") ?? "1"),
    notes: String(formData.get("notes") ?? "").trim(),
    scope: (formData.get("scope") as "company" | "personal" | null) ?? "company",
  };
}

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;
}

/**
 * Standard amortization (eşit taksitli kredi).
 * monthlyPayment = P × r × (1+r)^n / ((1+r)^n − 1)
 * where r = monthly interest rate (decimal), n = termMonths
 */
function computeAmortization(
  principal: number,
  monthlyRatePct: number,
  n: number,
): {
  monthlyPayment: number;
  schedule: Array<{ principalPart: number; interestPart: number; amount: number }>;
} {
  const r = monthlyRatePct / 100;
  let monthlyPayment: number;
  if (r === 0) {
    monthlyPayment = principal / n;
  } else {
    const factor = Math.pow(1 + r, n);
    monthlyPayment = (principal * r * factor) / (factor - 1);
  }
  monthlyPayment = Number(monthlyPayment.toFixed(2));

  const schedule: Array<{ principalPart: number; interestPart: number; amount: number }> = [];
  let remaining = principal;
  for (let i = 0; i < n; i++) {
    const interestPart = Number((remaining * r).toFixed(2));
    let principalPart = Number((monthlyPayment - interestPart).toFixed(2));
    // Final installment absorbs rounding drift
    if (i === n - 1) {
      principalPart = Number(remaining.toFixed(2));
    }
    const amount = Number((interestPart + principalPart).toFixed(2));
    remaining = Number((remaining - principalPart).toFixed(2));
    schedule.push({ principalPart, interestPart, amount });
  }
  return { monthlyPayment, schedule };
}

function shiftMonth(date: Date, monthsAhead: number, paymentDay: number): Date {
  const d = new Date(date.getFullYear(), date.getMonth() + monthsAhead, 1);
  // clamp paymentDay to last day of that month
  const lastDay = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
  d.setDate(Math.min(paymentDay, lastDay));
  return d;
}

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

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

  const { schedule, monthlyPayment } = computeAmortization(
    parsed.data.principal,
    parsed.data.interestRate,
    parsed.data.termMonths,
  );

  let loanId: string | null = null;
  const admin = createAdminClient();
  // Installments inherit the loan's scope so personal-loan installments stay
  // hidden from the rest of the team too.
  const rowPerms = scopedRowPermissions(ctx.tenantId, ctx.user.id, parsed.data.scope);
  try {
    const loan = await admin.tablesDB.createRow(
      DATABASE_ID,
      TABLES.bankLoans,
      ID.unique(),
      {
        tenantId: ctx.tenantId,
        createdBy: ctx.user.id,
        bankAccountId: parsed.data.bankAccountId,
        bankName: parsed.data.bankName,
        loanName: parsed.data.loanName,
        loanType: parsed.data.loanType,
        principal: parsed.data.principal,
        interestRate: parsed.data.interestRate,
        termMonths: parsed.data.termMonths,
        monthlyPayment,
        startDate: toIso(parsed.data.startDate),
        paymentDay: parsed.data.paymentDay,
        status: "active",
        notes: parsed.data.notes,
        scope: parsed.data.scope,
      },
      rowPerms,
    );
    loanId = loan.$id;

    const start = new Date(toIso(parsed.data.startDate));
    for (let i = 0; i < parsed.data.termMonths; i++) {
      const due = shiftMonth(start, i + 1, parsed.data.paymentDay ?? 1);
      const slice = schedule[i];
      await admin.tablesDB.createRow(
        DATABASE_ID,
        TABLES.loanInstallments,
        ID.unique(),
        {
          tenantId: ctx.tenantId,
          loanId,
          installmentNo: i + 1,
          dueDate: due.toISOString(),
          amount: slice.amount,
          principalPart: slice.principalPart,
          interestPart: slice.interestPart,
          paid: false,
        },
        rowPerms,
      );
    }

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "bank_loan",
      entityId: loan.$id,
      changes: { ...parsed.data, monthlyPayment, installments: parsed.data.termMonths },
    });
  } catch (e) {
    if (loanId) {
      // Best-effort rollback: delete partially-created installments + loan
      try {
        const partial = await admin.tablesDB.listRows({
          databaseId: DATABASE_ID,
          tableId: TABLES.loanInstallments,
          queries: [
            Query.equal("tenantId", ctx.tenantId),
            Query.equal("loanId", loanId),
            Query.limit(500),
          ],
        });
        for (const r of partial.rows) {
          await admin.tablesDB.deleteRow(DATABASE_ID, TABLES.loanInstallments, r.$id);
        }
        await admin.tablesDB.deleteRow(DATABASE_ID, TABLES.bankLoans, loanId);
      } catch {
        /* ignore */
      }
    }
    return { ok: false, error: appwriteError(e) };
  }

  revalidatePath("/finance/loans");
  return { ok: true, loanId };
}

export async function deleteLoanAction(formData: FormData): Promise<LoanActionState> {
  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.bankLoans,
      id,
    )) as unknown as BankLoan;
    if (existing.tenantId !== ctx.tenantId || !canAccessRow(existing, ctx.user.id)) {
      return { ok: false, error: "Erişim engellendi." };
    }

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

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

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "delete",
      entityType: "bank_loan",
      entityId: id,
      changes: { loanName: existing.loanName, installments: installments.rows.length },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

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

export async function payInstallmentAction(formData: FormData): Promise<LoanActionState> {
  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.loanInstallments,
      id,
    )) as unknown as LoanInstallment;
    if (existing.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }
    if (existing.paid) {
      return { ok: false, error: "Bu taksit zaten ödenmiş." };
    }

    const loan = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.bankLoans,
      existing.loanId,
    )) as unknown as BankLoan;
    if (!canAccessRow(loan, ctx.user.id)) {
      return { ok: false, error: "Erişim engellendi." };
    }

    const loanScope = loan.scope ?? "company";
    // Create finance entry: expense, linked. Inherit loan's scope so personal
    // installments don't leak into company finance.
    const fe = await tablesDB.createRow(
      DATABASE_ID,
      TABLES.financeEntries,
      ID.unique(),
      {
        tenantId: ctx.tenantId,
        createdBy: ctx.user.id,
        type: "expense",
        amount: existing.amount,
        date: new Date().toISOString(),
        description: `${loan.bankName} — ${loan.loanName} #${existing.installmentNo} taksit ödemesi`,
        bankAccountId: loan.bankAccountId,
        scope: loanScope,
      },
      scopedRowPermissions(ctx.tenantId, ctx.user.id, loanScope),
    );

    await tablesDB.updateRow(DATABASE_ID, TABLES.loanInstallments, id, {
      paid: true,
      paidAt: new Date().toISOString(),
      financeEntryId: fe.$id,
    });

    // If this was the last unpaid one, mark loan closed
    const remaining = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.loanInstallments,
      queries: [
        Query.equal("tenantId", ctx.tenantId),
        Query.equal("loanId", existing.loanId),
        Query.equal("paid", false),
        Query.limit(1),
      ],
    });
    if (remaining.rows.length === 0) {
      await tablesDB.updateRow(DATABASE_ID, TABLES.bankLoans, existing.loanId, {
        status: "closed",
      });
    }

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "loan_installment",
      entityId: id,
      changes: { paid: true, financeEntryId: fe.$id, amount: existing.amount },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e) };
  }

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

export async function unpayInstallmentAction(
  formData: FormData,
): Promise<LoanActionState> {
  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.loanInstallments,
      id,
    )) as unknown as LoanInstallment;
    if (existing.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Erişim engellendi." };
    }
    if (!existing.paid) return { ok: true };

    // Verify the parent loan is also accessible (handles personal scope).
    const parent = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.bankLoans,
      existing.loanId,
    )) as unknown as BankLoan;
    if (!canAccessRow(parent, ctx.user.id)) {
      return { ok: false, error: "Erişim engellendi." };
    }

    if (existing.financeEntryId) {
      try {
        await tablesDB.deleteRow(
          DATABASE_ID,
          TABLES.financeEntries,
          existing.financeEntryId,
        );
      } catch {
        /* ignore: maybe already gone */
      }
    }

    await tablesDB.updateRow(DATABASE_ID, TABLES.loanInstallments, id, {
      paid: false,
      paidAt: null,
      financeEntryId: null,
    });

    // If loan was closed, reopen it
    await tablesDB.updateRow(DATABASE_ID, TABLES.bankLoans, existing.loanId, {
      status: "active",
    });

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

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