"use server";

import { revalidatePath } from "next/cache";
import { AppwriteException, ID, Permission, Role } from "node-appwrite";
import { InputFile } from "node-appwrite/file";

import { logAudit } from "./audit";
import {
  BUCKETS,
  DATABASE_ID,
  TABLES,
  type Job,
  type JobFile,
  type JobFileKind,
} from "./schema";
import { createAdminClient } from "./server";
import { requireRole, requireTenant } from "./tenant-guard";
import type {
  JobFileActionState,
  JobFileUploadState,
} from "./job-file-types";

const MAX_FILE_BYTES = 200 * 1024 * 1024; // 200MB — bucket limit

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 classifyFile(mimeType: string | undefined, name: string): JobFileKind {
  const lower = (mimeType || name).toLowerCase();
  if (/\.(stl|obj|ply|3mf|dcm)$/i.test(name)) return "scan";
  if (lower.startsWith("image/") || /\.(png|jpe?g|webp|tiff?|heic|heif|bmp)$/i.test(name)) {
    return "image";
  }
  return "document";
}

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

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

export async function uploadJobFilesAction(
  _prev: JobFileUploadState,
  formData: FormData,
): Promise<JobFileUploadState> {
  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"]);
  } catch {
    return { ok: false, error: "Yüklemek için yetkiniz yok." };
  }

  const job = await loadJobForTenant(jobId, ctx.tenantId);
  if (!job) return { ok: false, error: "İş bulunamadı." };

  const files = formData.getAll("files").filter((v): v is File => v instanceof File && v.size > 0);
  console.log("[uploadJobFilesAction] jobId=%s files=%d total=%dB", jobId, files.length, files.reduce((s, f) => s + f.size, 0));
  if (files.length === 0) {
    return { ok: false, error: "Dosya seçin." };
  }

  for (const f of files) {
    if (f.size > MAX_FILE_BYTES) {
      return { ok: false, error: `${f.name} 200MB sınırını aşıyor.` };
    }
  }

  const { storage, tablesDB } = createAdminClient();
  const uploadedFileIds: string[] = [];
  const createdRowIds: string[] = [];

  try {
    for (const f of files) {
      const fileId = ID.unique();
      const buffer = Buffer.from(await f.arrayBuffer());
      const inputFile = InputFile.fromBuffer(buffer, f.name);
      await storage.createFile({
        bucketId: BUCKETS.jobFiles,
        fileId,
        file: inputFile,
        permissions: filePermissions(job.clinicTenantId, job.labTenantId),
      });
      uploadedFileIds.push(fileId);

      const kind = classifyFile(f.type, f.name);
      const row = await tablesDB.createRow(
        DATABASE_ID,
        TABLES.jobFiles,
        ID.unique(),
        {
          jobId: job.$id,
          clinicTenantId: job.clinicTenantId,
          labTenantId: job.labTenantId,
          uploadedBy: ctx.user.id,
          kind,
          fileId,
          name: f.name.slice(0, 255),
          size: f.size,
          mimeType: f.type ? f.type.slice(0, 100) : undefined,
        },
        [
          Permission.read(Role.team(job.clinicTenantId)),
          Permission.read(Role.team(job.labTenantId)),
          Permission.delete(Role.team(job.clinicTenantId, "owner")),
          Permission.delete(Role.team(job.clinicTenantId, "admin")),
          Permission.delete(Role.team(job.labTenantId, "owner")),
          Permission.delete(Role.team(job.labTenantId, "admin")),
        ],
      );
      createdRowIds.push(row.$id);
    }

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "create",
      entityType: "job_files",
      entityId: jobId,
      changes: { count: files.length },
    });
  } catch (e) {
    console.error("[uploadJobFilesAction] failed", e);
    // Rollback: best-effort cleanup of partially uploaded files and rows.
    for (const id of createdRowIds) {
      try {
        await tablesDB.deleteRow(DATABASE_ID, TABLES.jobFiles, id);
      } catch {
        /* ignore */
      }
    }
    for (const id of uploadedFileIds) {
      try {
        await storage.deleteFile({ bucketId: BUCKETS.jobFiles, fileId: id });
      } catch {
        /* ignore */
      }
    }
    return { ok: false, error: appwriteError(e, "Dosya yüklenemedi.") };
  }

  revalidatePath(`/jobs/${jobId}`);
  return { ok: true, uploaded: files.length };
}

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

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

  try {
    const { storage, tablesDB } = createAdminClient();
    const row = (await tablesDB.getRow(
      DATABASE_ID,
      TABLES.jobFiles,
      rowId,
    )) as unknown as JobFile;
    if (
      row.clinicTenantId !== ctx.tenantId &&
      row.labTenantId !== ctx.tenantId
    ) {
      return { ok: false, error: "Yetkiniz yok." };
    }

    await tablesDB.deleteRow(DATABASE_ID, TABLES.jobFiles, rowId);
    try {
      await storage.deleteFile({ bucketId: BUCKETS.jobFiles, fileId: row.fileId });
    } catch {
      // File may already be gone; row is the source of truth.
    }

    await logAudit({
      tenantId: ctx.tenantId,
      userId: ctx.user.id,
      action: "delete",
      entityType: "job_file",
      entityId: rowId,
      changes: { fileId: row.fileId, name: row.name },
    });

    revalidatePath(`/jobs/${row.jobId}`);
    return { ok: true };
  } catch (e) {
    return { ok: false, error: appwriteError(e, "Silinemedi.") };
  }
}
