"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { after } from "next/server";
import {
  projectFormValues,
  validateProject,
  type ProjectFieldErrors,
  type ProjectFormValues,
} from "@/lib/validation/project";
import type { ImportPreview } from "@/lib/import/rows";
import { canEdit, VIEW_ONLY_ERROR } from "@/lib/auth/roles";
import { requireUser } from "@/server/auth/session";
import { checkNewDomainsQuietly } from "@/server/domains/service";
import { ProjectNotCheckableError, checkProject, checkProjectQuietly, checkProjectsQuietly } from "@/server/health/runner";
import { ImportFileError, SpreadsheetError, importProjects, previewImport } from "@/server/import/service";
import { logger } from "@/server/logger";
import {
  DuplicateProjectCodeError,
  ProjectNotFoundError,
  createProject,
  setMonitoringEnabled,
  softDeleteProject,
  updateProject,
} from "@/server/projects/service";

export type ProjectFormState = {
  values?: ProjectFormValues;
  fieldErrors?: ProjectFieldErrors;
  error?: string;
};

export type ActionResult = { ok: true } | { ok: false; error: string };

function revalidateProjectViews(projectId?: string) {
  revalidatePath("/");
  revalidatePath("/apps");
  if (projectId) revalidatePath(`/apps/${projectId}`);
}

// New, re-enabled or re-targeted apps get checked straight away instead of
// showing "Not checked yet" (or a stale status) until the next scheduled run.
// Runs after the response so saving never waits on a slow health API.
// A domain nobody has looked up yet is looked up too, instead of waiting for the daily run.
function checkInBackground(projectId: string) {
  after(() => checkProjectQuietly(projectId, "MANUAL"));
  after(() => checkNewDomainsQuietly());
}

async function saveProject(id: string | null, formData: FormData): Promise<ProjectFormState> {
  const user = await requireUser();
  if (!canEdit(user)) return { error: VIEW_ONLY_ERROR };
  const values = projectFormValues(formData);

  const validated = validateProject(values);
  if (!validated.ok) return { values, fieldErrors: validated.fieldErrors };

  try {
    if (id) {
      const { needsCheck } = await updateProject(id, validated.data);
      logger.info("project.updated", { projectId: id, projectCode: validated.data.projectCode, by: user.username });
      if (needsCheck) checkInBackground(id);
    } else {
      const project = await createProject(validated.data);
      logger.info("project.created", { projectId: project.id, projectCode: project.projectCode, by: user.username });
      if (project.monitoringEnabled) checkInBackground(project.id);
    }
  } catch (error) {
    if (error instanceof DuplicateProjectCodeError) {
      return { values, fieldErrors: { projectCode: "This Project Code is already in use" } };
    }
    if (error instanceof ProjectNotFoundError) {
      return { values, error: "This application no longer exists. It may have been deleted." };
    }
    logger.error("project.save.failed", { projectId: id, error });
    return { values, error: "Something went wrong while saving. Please try again." };
  }

  revalidateProjectViews();
  redirect("/apps");
}

export async function createProjectAction(_prev: ProjectFormState, formData: FormData) {
  return saveProject(null, formData);
}

export async function updateProjectAction(id: string, _prev: ProjectFormState, formData: FormData) {
  return saveProject(id, formData);
}

export async function setMonitoringAction(id: string, enabled: boolean): Promise<ActionResult> {
  const user = await requireUser();
  if (!canEdit(user)) return { ok: false, error: VIEW_ONLY_ERROR };
  if (typeof id !== "string" || typeof enabled !== "boolean") return { ok: false, error: "Invalid request." };

  try {
    await setMonitoringEnabled(id, enabled);
    logger.info(enabled ? "project.monitoring.enabled" : "project.monitoring.disabled", {
      projectId: id,
      by: user.username,
    });
    if (enabled) checkInBackground(id);
  } catch (error) {
    if (error instanceof ProjectNotFoundError) return { ok: false, error: "This application no longer exists." };
    logger.error("project.monitoring.failed", { projectId: id, error });
    return { ok: false, error: "Could not update monitoring. Please try again." };
  }

  revalidateProjectViews();
  return { ok: true };
}

export async function deleteProjectAction(id: string): Promise<ActionResult> {
  const user = await requireUser();
  if (!canEdit(user)) return { ok: false, error: VIEW_ONLY_ERROR };
  if (typeof id !== "string") return { ok: false, error: "Invalid request." };

  try {
    await softDeleteProject(id);
    logger.info("project.deleted", { projectId: id, by: user.username });
  } catch (error) {
    // Already gone is the outcome the user wanted.
    if (!(error instanceof ProjectNotFoundError)) {
      logger.error("project.delete.failed", { projectId: id, error });
      return { ok: false, error: "Could not delete the application. Please try again." };
    }
  }

  revalidateProjectViews();
  return { ok: true };
}

export type CheckNowResult =
  | {
      ok: true;
      status: "WORKING" | "SLOW" | "NOT_WORKING";
      httpStatusCode: number | null;
      responseTimeMs: number | null;
      errorMessage: string | null;
    }
  | { ok: false; error: string };

/** Manual "Check Now": exactly the same check as the scheduler, awaited so the UI can show the result. */
export async function checkNowAction(id: string): Promise<CheckNowResult> {
  const user = await requireUser();
  if (!canEdit(user)) return { ok: false, error: VIEW_ONLY_ERROR };
  if (typeof id !== "string") return { ok: false, error: "Invalid request." };

  logger.info("health.check.manual", { projectId: id, by: user.username });

  try {
    const result = await checkProject(id, "MANUAL");
    revalidateProjectViews(id);
    return {
      ok: true,
      status: result.status,
      httpStatusCode: result.httpStatusCode,
      responseTimeMs: result.responseTimeMs,
      errorMessage: result.errorMessage,
    };
  } catch (error) {
    if (error instanceof ProjectNotCheckableError) {
      return {
        ok: false,
        error:
          error.reason === "monitoring_disabled"
            ? "Monitoring is disabled for this application."
            : "This application no longer exists.",
      };
    }
    logger.error("health.check.manual.failed", { projectId: id, error });
    return { ok: false, error: "The health check could not be completed. Please try again." };
  }
}

// ---------------------------------------------------------------------------
// Import from a sheet
// ---------------------------------------------------------------------------

export type ImportPreviewResult = { ok: true; fileName: string; preview: ImportPreview } | { ok: false; error: string };

/** Step 1: parse the uploaded sheet and report which rows are new, existing, duplicate or invalid. Nothing is saved. */
type UploadedFile = { name: string; size: number; arrayBuffer: () => Promise<ArrayBuffer> };

// Duck-typed on purpose: the File the action runtime hands over may come from
// another realm (Node's undici vs. the bundled one), so `instanceof File` is unreliable.
function asUploadedFile(value: FormDataEntryValue | null): UploadedFile | null {
  if (typeof value !== "object" || value === null) return null;
  const file = value as Partial<UploadedFile>;
  if (typeof file.arrayBuffer !== "function" || typeof file.size !== "number") return null;
  return { name: typeof file.name === "string" ? file.name : "upload", size: file.size, arrayBuffer: () => file.arrayBuffer!() };
}

export async function previewImportAction(formData: FormData): Promise<ImportPreviewResult> {
  const user = await requireUser();
  if (!canEdit(user)) return { ok: false, error: VIEW_ONLY_ERROR };
  const file = asUploadedFile(formData.get("file"));
  if (!file || file.size === 0) return { ok: false, error: "Choose a .csv or .xlsx file to import." };

  try {
    const preview = await previewImport(file.name, Buffer.from(await file.arrayBuffer()));
    logger.info("import.previewed", { fileName: file.name, rows: preview.rows.length, ...preview.counts, by: user.username });
    return { ok: true, fileName: file.name, preview };
  } catch (error) {
    if (error instanceof ImportFileError || error instanceof SpreadsheetError) return { ok: false, error: error.message };
    logger.error("import.preview.failed", { fileName: file.name, error });
    return { ok: false, error: "The file could not be read. Please try again." };
  }
}

export type ImportResult =
  | { ok: true; created: number; updated: number; unchanged: number; failed: Array<{ projectCode: string; error: string }> }
  | { ok: false; error: string };

/** Step 2: create the selected new apps and update the selected existing ones, then health-check them in the background. */
export async function importProjectsAction(rows: unknown): Promise<ImportResult> {
  const user = await requireUser();
  if (!canEdit(user)) return { ok: false, error: VIEW_ONLY_ERROR };
  if (!Array.isArray(rows) || rows.length === 0) return { ok: false, error: "Select at least one row to import." };
  if (rows.length > 2000) return { ok: false, error: "At most 2000 rows can be imported at once." };

  try {
    const outcome = await importProjects(rows, user.username);
    if (outcome.checkIds.length > 0) {
      after(() => checkProjectsQuietly(outcome.checkIds, "MANUAL"));
      after(() => checkNewDomainsQuietly());
    }
    revalidateProjectViews();
    return { ok: true, created: outcome.created, updated: outcome.updated, unchanged: outcome.unchanged, failed: outcome.failed };
  } catch (error) {
    logger.error("import.failed", { error });
    return { ok: false, error: "The import could not be completed. Please try again." };
  }
}
