"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 Patient } from "./schema";
import { createAdminClient } from "./server";
import { requireRole, requireTenant, requireTenantKind } from "./tenant-guard";
import type {
  PatientActionState,
  PatientFormState,
} from "./patient-types";
import { patientSchema } from "@/lib/validation/patient";

const CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";

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 pickFields(formData: FormData) {
  return {
    patientCode: String(formData.get("patientCode") ?? "").trim(),
    firstName: String(formData.get("firstName") ?? "").trim(),
    lastName: String(formData.get("lastName") ?? "").trim(),
    phone: String(formData.get("phone") ?? "").trim(),
    dateOfBirth: String(formData.get("dateOfBirth") ?? "").trim(),
    notes: String(formData.get("notes") ?? "").trim(),
  };
}

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

function generateCode(): string {
  let out = "";
  for (let i = 0; i < 6; i++) {
    out += CODE_ALPHABET[Math.floor(Math.random() * CODE_ALPHABET.length)];
  }
  return out;
}

async function reserveUniqueCode(clinicTenantId: string): Promise<string> {
  const { tablesDB } = createAdminClient();
  for (let attempt = 0; attempt < 8; attempt++) {
    const candidate = generateCode();
    const existing = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.patients,
      queries: [
        Query.equal("clinicTenantId", clinicTenantId),
        Query.equal("patientCode", candidate),
        Query.limit(1),
      ],
    });
    if (existing.total === 0) return candidate;
  }
  throw new Error("PATIENT_CODE_GENERATION_FAILED");
}

export async function createPatientAction(
  _prev: PatientFormState,
  formData: FormData,
): Promise<PatientFormState> {
  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin", "member"]);
    requireTenantKind(ctx, ["clinic"]);
  } catch {
    return { ok: false, error: "Hasta kaydı yalnızca klinik hesapları için." };
  }

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

  const { tablesDB } = createAdminClient();

  // If user supplied a code, make sure it isn't already in use within this clinic.
  let code = parsed.data.patientCode;
  if (code) {
    const dup = await tablesDB.listRows({
      databaseId: DATABASE_ID,
      tableId: TABLES.patients,
      queries: [
        Query.equal("clinicTenantId", ctx.tenantId),
        Query.equal("patientCode", code),
        Query.limit(1),
      ],
    });
    if (dup.total > 0) {
      return {
        ok: false,
        error: "Bu protokol no kayıtlı.",
        fieldErrors: { patientCode: "Bu kod başka bir hasta için kullanılmış." },
      };
    }
  } else {
    code = await reserveUniqueCode(ctx.tenantId);
  }

  try {
    const created = await tablesDB.createRow(
      DATABASE_ID,
      TABLES.patients,
      ID.unique(),
      {
        clinicTenantId: ctx.tenantId,
        createdBy: ctx.user.id,
        patientCode: code,
        firstName: parsed.data.firstName,
        lastName: parsed.data.lastName,
        phone: parsed.data.phone,
        dateOfBirth: parsed.data.dateOfBirth,
        notes: parsed.data.notes,
        archived: false,
      },
      patientPermissions(ctx.tenantId),
    );
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "patient",
      entityId: created.$id,
      changes: { patientCode: code, firstName: parsed.data.firstName },
    });
    revalidatePath("/patients");
    return { ok: true, patientId: created.$id };
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Hasta eklenemedi.") };
  }
}

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

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

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

  try {
    const { tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.patients,
      id,
    )) as unknown as Patient;
    if (row.clinicTenantId !== ctx.tenantId) {
      return { ok: false, error: "Bu hastayı düzenleme yetkiniz yok." };
    }

    // patientCode is intentionally NOT updatable here — re-keying historical
    // jobs would be confusing. Only firstName/lastName/phone/dateOfBirth/notes.
    await tablesDB.updateRow(DATABASE_ID, TABLES.patients, id, {
      firstName: parsed.data.firstName,
      lastName: parsed.data.lastName,
      phone: parsed.data.phone,
      dateOfBirth: parsed.data.dateOfBirth,
      notes: parsed.data.notes,
    });
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "patient",
      entityId: id,
      changes: parsed.data,
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Güncellenemedi.") };
  }

  revalidatePath("/patients");
  return { ok: true, patientId: id };
}

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

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

  try {
    const { tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.patients,
      id,
    )) as unknown as Patient;
    if (row.clinicTenantId !== ctx.tenantId) {
      return { ok: false, error: "Yetkiniz yok." };
    }
    await tablesDB.updateRow(DATABASE_ID, TABLES.patients, id, {
      archived: !row.archived,
    });
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "patient",
      entityId: id,
      changes: { archived: !row.archived },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "İşlem başarısız.") };
  }

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