"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 { syncFinanceForJob } from "./finance-sync";
import { createNotification } from "./notification-helpers";
import { calculateJobPrice } from "./pricing";
import {
  DATABASE_ID,
  TABLES,
  type Connection,
  type Job,
  type JobStep,
  type Patient,
} from "./schema";
import { createAdminClient } from "./server";
import { requireRole, requireTenant, requireTenantKind } from "./tenant-guard";
import { JOB_STEP_ORDER } from "./job-types";
import type { JobActionState, JobFormState } from "./job-types";
import { createJobSchema } from "@/lib/validation/job";

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 {
    labTenantId: String(formData.get("labTenantId") ?? "").trim(),
    patientId: String(formData.get("patientId") ?? "").trim(),
    patientCode: String(formData.get("patientCode") ?? "").trim(),
    prostheticType: String(formData.get("prostheticType") ?? "").trim(),
    teeth: formData.getAll("teeth").map((v) => String(v).trim()).filter(Boolean),
    color: String(formData.get("color") ?? "").trim(),
    description: String(formData.get("description") ?? "").trim(),
    dueDate: String(formData.get("dueDate") ?? "").trim(),
  };
}

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

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

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

  const { tablesDB } = createAdminClient();

  // If a patientId is supplied, verify it belongs to this clinic and inherit
  // its patientCode so the lab side always sees a stable identifier.
  let patientCode = parsed.data.patientCode;
  if (parsed.data.patientId) {
    try {
      const patientRow = (await tablesDB.getRow(
        DATABASE_ID,
        TABLES.patients,
        parsed.data.patientId,
      )) as unknown as Patient;
      if (patientRow.clinicTenantId !== ctx.tenantId) {
        return {
          ok: false,
          error: "Bu hasta size ait değil.",
          fieldErrors: { patientId: "Yetki yok." },
        };
      }
      patientCode = patientRow.patientCode;
    } catch {
      return {
        ok: false,
        error: "Hasta bulunamadı.",
        fieldErrors: { patientId: "Hasta kaydı yok." },
      };
    }
  }

  // Verify the chosen lab is an approved connection of this clinic
  const connRes = await tablesDB.listRows({
    databaseId: DATABASE_ID,
    tableId: TABLES.connections,
    queries: [
      Query.equal("clinicTenantId", ctx.tenantId),
      Query.equal("labTenantId", parsed.data.labTenantId),
      Query.equal("status", "approved"),
      Query.limit(1),
    ],
  });
  const conn = connRes.rows[0] as unknown as Connection | undefined;
  if (!conn) {
    return {
      ok: false,
      error: "Seçilen laboratuvarla onaylanmış bir bağlantınız yok.",
      fieldErrors: { labTenantId: "Onaylı bağlantı bulunamadı." },
    };
  }

  try {
    // Server-side price calculation — clinic never sets the price.
    const quote = await calculateJobPrice({
      labTenantId: parsed.data.labTenantId,
      clinicTenantId: ctx.tenantId,
      prostheticType: parsed.data.prostheticType,
      teethCount: parsed.data.teeth.length,
    });

    const created = await tablesDB.createRow(
      DATABASE_ID,
      TABLES.jobs,
      ID.unique(),
      {
        clinicTenantId: ctx.tenantId,
        labTenantId: parsed.data.labTenantId,
        createdBy: ctx.user.id,
        patientId: parsed.data.patientId,
        patientCode,
        prostheticType: parsed.data.prostheticType,
        memberCount: parsed.data.teeth.length,
        teeth: parsed.data.teeth,
        color: parsed.data.color,
        description: parsed.data.description,
        price: quote?.amount,
        currency: quote?.currency,
        dueDate: parsed.data.dueDate,
        status: "pending",
      },
      jobPermissions(ctx.tenantId, parsed.data.labTenantId),
    );
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "job",
      entityId: created.$id,
      changes: { labTenantId: parsed.data.labTenantId, patientCode },
    });
    await createNotification({
      tenantId: parsed.data.labTenantId,
      jobId: created.$id,
      message: `${ctx.settings?.companyName ?? "Bir klinik"} yeni iş yayınladı (${patientCode}).`,
    });
    revalidatePath("/jobs/outbound");
    revalidatePath("/dashboard");
    return { ok: true, jobId: created.$id };
  } catch (e) {
    return { ok: false, error: appwriteError(e, "İş oluşturulamadı.") };
  }
}

function jobHistoryPermissions(clinicTenantId: string, labTenantId: string): string[] {
  return [
    Permission.read(Role.team(clinicTenantId)),
    Permission.read(Role.team(labTenantId)),
  ];
}

async function loadJobForTenant(jobId: string, tenantId: string): Promise<Job | null> {
  try {
    const { tablesDB } = createAdminClient();
    const row = await tablesDB.getRow(DATABASE_ID, TABLES.jobs, jobId);
    const job = row as unknown as Job;
    if (job.clinicTenantId !== tenantId && job.labTenantId !== tenantId) {
      return null;
    }
    return job;
  } catch {
    return null;
  }
}

async function appendJobHistory(args: {
  job: Job;
  step: JobStep;
  completedBy: string;
  note?: string;
}): Promise<void> {
  const { tablesDB } = createAdminClient();
  try {
    await tablesDB.createRow(
      DATABASE_ID,
      TABLES.jobStatusHistory,
      ID.unique(),
      {
        jobId: args.job.$id,
        clinicTenantId: args.job.clinicTenantId,
        labTenantId: args.job.labTenantId,
        step: args.step,
        completedBy: args.completedBy,
        completedAt: new Date().toISOString(),
        note: args.note,
      },
      jobHistoryPermissions(args.job.clinicTenantId, args.job.labTenantId),
    );
  } catch {
    // history failures must never block the main mutation
  }
}

export async function acceptJobAction(
  _prev: JobActionState,
  formData: FormData,
): Promise<JobActionState> {
  const jobId = String(formData.get("jobId") ?? "").trim();
  if (!jobId) return { ok: false, error: "İş bulunamadı." };

  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin", "member"]);
    requireTenantKind(ctx, ["lab"]);
  } catch {
    return { ok: false, error: "Sadece laboratuvar bu işi kabul edebilir." };
  }

  const job = await loadJobForTenant(jobId, ctx.tenantId);
  if (!job || job.labTenantId !== ctx.tenantId) {
    return { ok: false, error: "İş bulunamadı." };
  }
  if (job.status !== "pending") {
    return { ok: false, error: "Bu iş zaten işleme alınmış." };
  }

  try {
    const { tablesDB } = createAdminClient();
    await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
      status: "in_progress",
      currentStep: "olcu",
    });
    await appendJobHistory({ job, step: "olcu", completedBy: ctx.user.id });
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "job",
      entityId: jobId,
      changes: { status: "in_progress", currentStep: "olcu" },
    });
    await createNotification({
      tenantId: job.clinicTenantId,
      jobId,
      message: `${ctx.settings?.companyName ?? "Lab"} hasta ${job.patientCode} işini işleme aldı.`,
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Kabul edilemedi.") };
  }

  revalidatePath(`/jobs/${jobId}`);
  revalidatePath("/jobs/inbound");
  revalidatePath("/jobs/outbound");
  return { ok: true };
}

export async function advanceStepAction(
  _prev: JobActionState,
  formData: FormData,
): Promise<JobActionState> {
  const jobId = String(formData.get("jobId") ?? "").trim();
  if (!jobId) return { ok: false, error: "İş bulunamadı." };
  const note = String(formData.get("note") ?? "").trim() || undefined;

  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin", "member"]);
    requireTenantKind(ctx, ["lab"]);
  } catch {
    return { ok: false, error: "Sadece laboratuvar aşama ilerletebilir." };
  }

  const job = await loadJobForTenant(jobId, ctx.tenantId);
  if (!job || job.labTenantId !== ctx.tenantId) {
    return { ok: false, error: "İş bulunamadı." };
  }
  if (job.status !== "in_progress") {
    return { ok: false, error: "Yalnızca işleme alınmış işler ilerletilebilir." };
  }
  const currentIdx = job.currentStep ? JOB_STEP_ORDER.indexOf(job.currentStep) : -1;
  if (currentIdx < 0) return { ok: false, error: "Mevcut aşama bilinmiyor." };

  const nextIdx = currentIdx + 1;
  const isFinalStepComplete = currentIdx === JOB_STEP_ORDER.length - 1;

  try {
    const { tablesDB } = createAdminClient();
    if (isFinalStepComplete) {
      await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
        status: "sent",
      });
      await logAudit({
        tenantId: ctx.tenantId,
        userId: ctx.user.id,
        action: "update",
        entityType: "job",
        entityId: jobId,
        changes: { status: "sent" },
      });
    } else {
      const nextStep = JOB_STEP_ORDER[nextIdx];
      await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
        currentStep: nextStep,
      });
      await appendJobHistory({
        job,
        step: job.currentStep!,
        completedBy: ctx.user.id,
        note,
      });
      await logAudit({
        tenantId: ctx.tenantId,
        userId: ctx.user.id,
        action: "update",
        entityType: "job",
        entityId: jobId,
        changes: { currentStep: nextStep, completedStep: job.currentStep },
      });
    }
  } catch (e) {
    return { ok: false, error: appwriteError(e, "İlerletilemedi.") };
  }

  if (isFinalStepComplete) {
    // Record completion of the last step too, then mark sent.
    await appendJobHistory({
      job,
      step: job.currentStep!,
      completedBy: ctx.user.id,
      note,
    });
    await syncFinanceForJob({ ...job, status: "sent" });
    await createNotification({
      tenantId: job.clinicTenantId,
      jobId,
      message: `Hasta ${job.patientCode} işi gönderildi. Teslim alındığında onaylayın.`,
    });
  }

  revalidatePath(`/jobs/${jobId}`);
  revalidatePath("/jobs/inbound");
  revalidatePath("/jobs/outbound");
  revalidatePath("/finance");
  return { ok: true };
}

export async function markDeliveredAction(
  _prev: JobActionState,
  formData: FormData,
): Promise<JobActionState> {
  const jobId = String(formData.get("jobId") ?? "").trim();
  if (!jobId) return { ok: false, error: "İş bulunamadı." };

  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin", "member"]);
    requireTenantKind(ctx, ["clinic"]);
  } catch {
    return { ok: false, error: "Teslim almayı yalnızca klinik yapabilir." };
  }

  const job = await loadJobForTenant(jobId, ctx.tenantId);
  if (!job || job.clinicTenantId !== ctx.tenantId) {
    return { ok: false, error: "İş bulunamadı." };
  }
  if (job.status !== "sent") {
    return { ok: false, error: "Sadece gönderilmiş işler teslim alınabilir." };
  }

  try {
    const { tablesDB } = createAdminClient();
    await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
      status: "delivered",
    });
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "job",
      entityId: jobId,
      changes: { status: "delivered" },
    });
    await syncFinanceForJob({ ...job, status: "delivered" });
    await createNotification({
      tenantId: job.labTenantId,
      jobId,
      message: `Hasta ${job.patientCode} işi teslim alındı.`,
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Teslim alınamadı.") };
  }

  revalidatePath(`/jobs/${jobId}`);
  revalidatePath("/jobs/outbound");
  revalidatePath("/jobs/inbound");
  revalidatePath("/finance");
  return { ok: true };
}

export async function cancelJobAction(
  _prev: JobActionState,
  formData: FormData,
): Promise<JobActionState> {
  const jobId = String(formData.get("jobId") ?? "").trim();
  if (!jobId) return { ok: false, error: "İş bulunamadı." };

  let ctx;
  try {
    ctx = await requireTenant();
    requireRole(ctx, ["owner", "admin"]);
  } catch {
    return { ok: false, error: "Bu işlem için yetkiniz yok." };
  }

  const job = await loadJobForTenant(jobId, ctx.tenantId);
  if (!job) return { ok: false, error: "İş bulunamadı." };
  if (job.status !== "pending") {
    return { ok: false, error: "Yalnızca bekleyen işler iptal edilebilir." };
  }
  if (ctx.kind === "clinic" && job.clinicTenantId !== ctx.tenantId) {
    return { ok: false, error: "Yetkiniz yok." };
  }
  if (ctx.kind === "lab" && job.labTenantId !== ctx.tenantId) {
    return { ok: false, error: "Yetkiniz yok." };
  }

  try {
    const { tablesDB } = createAdminClient();
    await tablesDB.updateRow(DATABASE_ID, TABLES.jobs, jobId, {
      status: "cancelled",
    });
    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "update",
      entityType: "job",
      entityId: jobId,
      changes: { status: "cancelled" },
    });
  } catch (e) {
    return { ok: false, error: appwriteError(e, "İptal edilemedi.") };
  }

  revalidatePath(`/jobs/${jobId}`);
  revalidatePath("/jobs/inbound");
  revalidatePath("/jobs/outbound");
  return { ok: true };
}
