"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 { createNotification } from "./notification-helpers";
import {
  DATABASE_ID,
  TABLES,
  type Connection,
  type Payment,
} from "./schema";
import { createAdminClient } from "./server";
import { requireRole, requireTenant } from "./tenant-guard";
import type {
  PaymentActionState,
  PaymentFormState,
} from "./payment-types";
import { paymentSchema } from "@/lib/validation/payment";

function appwriteError(e: unknown, fallback = "Beklenmeyen bir hata oluştu."): string {
  if (e instanceof AppwriteException) return e.message || fallback;
  return process.env.NODE_ENV !== "production" && e instanceof Error
    ? `${fallback} (${e.message})`
    : fallback;
}

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 paymentPermissions(tenantId: string, counterpartTenantId: string): string[] {
  return [
    Permission.read(Role.team(tenantId)),
    Permission.read(Role.team(counterpartTenantId)),
    Permission.update(Role.team(tenantId, "owner")),
    Permission.update(Role.team(tenantId, "admin")),
    Permission.delete(Role.team(tenantId, "owner")),
    Permission.delete(Role.team(tenantId, "admin")),
  ];
}

function pickFields(formData: FormData) {
  return {
    counterpartTenantId: String(formData.get("counterpartTenantId") ?? "").trim(),
    amount: String(formData.get("amount") ?? "").trim(),
    currency: String(formData.get("currency") ?? "").trim(),
    paymentDate: String(formData.get("paymentDate") ?? "").trim(),
    method: String(formData.get("method") ?? "").trim(),
    notes: String(formData.get("notes") ?? "").trim(),
  };
}

/**
 * Record a payment between this tenant and a counterpart. Direction is
 * determined by the caller's tenant kind:
 *   - lab    → inflow  (the lab received money from a clinic)
 *   - clinic → outflow (the clinic paid a lab)
 *
 * A single payment can cover many invoices at once — we do NOT walk back
 * into finance_entries to settle individual rows. The open balance per
 * connection is always computed live as (sum of receivables for that
 * counterpart) - (sum of payments inflow from that counterpart) for the
 * lab side, and the symmetric formula for the clinic side.
 */
export async function recordPaymentAction(
  _prev: PaymentFormState,
  formData: FormData,
): Promise<PaymentFormState> {
  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin"]);
  } catch {
    return { ok: false, error: "Ödeme kaydı için yetkiniz yok." };
  }
  if (!ctx.kind) {
    return { ok: false, error: "Tenant türü bilinmiyor." };
  }
  const direction = ctx.kind === "lab" ? "inflow" : "outflow";

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

  const { tablesDB } = createAdminClient();

  // Counterpart must be an approved connection — we never let a tenant
  // record payments against an unconnected workspace.
  const labId = ctx.kind === "lab" ? ctx.tenantId : parsed.data.counterpartTenantId;
  const clinicId = ctx.kind === "lab" ? parsed.data.counterpartTenantId : ctx.tenantId;
  const connRes = await tablesDB.listRows({
    databaseId: DATABASE_ID,
    tableId: TABLES.connections,
    queries: [
      Query.equal("labTenantId", labId),
      Query.equal("clinicTenantId", clinicId),
      Query.equal("status", "approved"),
      Query.limit(1),
    ],
  });
  if (!(connRes.rows[0] as unknown as Connection | undefined)) {
    return {
      ok: false,
      error: "Onaylı bir bağlantı bulunamadı.",
      fieldErrors: { counterpartTenantId: "Bağlantı yok." },
    };
  }

  // Lab-recorded payments are self-confirmed (the lab knows it received
  // the money). Clinic-recorded payments enter as pending and require the
  // lab to confirm before they're counted in the open balance.
  const status = ctx.kind === "lab" ? "confirmed" : "pending";

  try {
    const created = await tablesDB.createRow(
      DATABASE_ID,
      TABLES.payments,
      ID.unique(),
      {
        tenantId: ctx.tenantId,
        counterpartTenantId: parsed.data.counterpartTenantId,
        direction,
        amount: parsed.data.amount,
        currency: parsed.data.currency,
        paymentDate: parsed.data.paymentDate,
        method: parsed.data.method,
        notes: parsed.data.notes,
        status,
        recordedBy: ctx.user.id,
      },
      paymentPermissions(ctx.tenantId, parsed.data.counterpartTenantId),
    );
    void logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "payment",
      entityId: created.$id,
      changes: {
        direction,
        amount: parsed.data.amount,
        counterpartTenantId: parsed.data.counterpartTenantId,
        status,
      },
    });
    // Notify the lab when a clinic submits a payment for approval.
    if (status === "pending") {
      void createNotification({
        tenantId: parsed.data.counterpartTenantId,
        message: `Yeni ödeme bildirimi: ${parsed.data.amount.toLocaleString("tr-TR")} ${parsed.data.currency} — onayınızı bekliyor.`,
      });
    }
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Ödeme kaydedilemedi.") };
  }

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

/**
 * Lab confirms a payment the clinic claimed to have made. The row stays
 * with the clinic as the recorder (tenantId), only its status flips so
 * the balance computation starts counting it.
 */
export async function confirmPaymentAction(
  _prev: PaymentActionState,
  formData: FormData,
): Promise<PaymentActionState> {
  const id = String(formData.get("id") ?? "").trim();
  if (!id) return { ok: false, error: "Ödeme kaydı bulunamadı." };

  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin"]);
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }
  if (ctx.kind !== "lab") {
    return { ok: false, error: "Onayı yalnızca laboratuvar verebilir." };
  }

  try {
    const { tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.payments,
      id,
    )) as unknown as Payment;
    // Lab can only confirm payments where IT is the counterpart and the
    // clinic was the recorder. Anything else is a permission error.
    if (row.counterpartTenantId !== ctx.tenantId) {
      return { ok: false, error: "Bu ödeme size ait değil." };
    }
    if (row.status === "confirmed") {
      return { ok: true };
    }
    await tablesDB.updateRow(DATABASE_ID, TABLES.payments, id, {
      status: "confirmed",
    });
    void logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "payment",
      entityId: id,
      changes: { status: "confirmed" },
    });
    void createNotification({
      tenantId: row.tenantId,
      message: `Ödemeniz onaylandı: ${row.amount.toLocaleString("tr-TR")} ${row.currency}.`,
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Onaylanamadı.") };
  }

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

export async function rejectPaymentAction(
  _prev: PaymentActionState,
  formData: FormData,
): Promise<PaymentActionState> {
  const id = String(formData.get("id") ?? "").trim();
  if (!id) return { ok: false, error: "Ödeme kaydı bulunamadı." };

  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin"]);
  } catch {
    return { ok: false, error: "Yetkiniz yok." };
  }
  if (ctx.kind !== "lab") {
    return { ok: false, error: "Reddi yalnızca laboratuvar verebilir." };
  }

  try {
    const { tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.payments,
      id,
    )) as unknown as Payment;
    if (row.counterpartTenantId !== ctx.tenantId) {
      return { ok: false, error: "Bu ödeme size ait değil." };
    }
    await tablesDB.updateRow(DATABASE_ID, TABLES.payments, id, {
      status: "rejected",
    });
    void logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "payment",
      entityId: id,
      changes: { status: "rejected" },
    });
    void createNotification({
      tenantId: row.tenantId,
      message: `Ödeme bildiriminiz reddedildi: ${row.amount.toLocaleString("tr-TR")} ${row.currency}.`,
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Reddedilemedi.") };
  }

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

export async function deletePaymentAction(
  _prev: PaymentActionState,
  formData: FormData,
): Promise<PaymentActionState> {
  const id = String(formData.get("id") ?? "").trim();
  if (!id) return { ok: false, error: "Kayıt bulunamadı." };

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

  try {
    const { tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.payments,
      id,
    )) as unknown as Payment;
    if (row.tenantId !== ctx.tenantId) {
      return { ok: false, error: "Yetkiniz yok." };
    }
    await tablesDB.deleteRow(DATABASE_ID, TABLES.payments, id);
    void logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "delete",
      entityType: "payment",
      entityId: id,
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Silinemedi.") };
  }

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