import { z } from "zod";

// Shared by the browser form and the server actions — keep it free of
// server-only imports. The server always re-validates with the same schema.

// The timeout must exceed the slow-response threshold (Monitoring Settings,
// default 30 s), otherwise a slow API is reported as Not Working (timeout)
// before it can be observed as Slow.
export const PROJECT_DEFAULTS = {
  expectedHttpStatus: 200,
  timeoutSeconds: 60,
} as const;

export const PROJECT_LIMITS = {
  httpStatus: { min: 100, max: 599 },
  timeoutSeconds: { min: 1, max: 180 },
} as const;

// Link-local range used by cloud metadata services; never a legitimate health API.
const BLOCKED_HOSTS = /^(169\.254\.\d{1,3}\.\d{1,3}|\[?fe80:.*|metadata\.google\.internal)$/i;

export function parseHttpUrl(value: string): URL | null {
  try {
    const url = new URL(value);
    return url.protocol === "http:" || url.protocol === "https:" ? url : null;
  } catch {
    return null;
  }
}

/** True for hosts the health checker must never call. */
export function isBlockedHost(hostname: string): boolean {
  return BLOCKED_HOSTS.test(hostname);
}

function intField(label: string, min: number, max: number, fallback: number) {
  return z
    .string()
    .trim()
    .transform((value) => (value === "" ? String(fallback) : value))
    .pipe(
      z
        .string()
        .regex(/^\d+$/, `${label} must be a whole number`)
        .transform(Number)
        .pipe(
          z
            .number()
            .min(min, `${label} must be between ${min} and ${max}`)
            .max(max, `${label} must be between ${min} and ${max}`),
        ),
    );
}

export const projectSchema = z.object({
  projectCode: z
    .string()
    .trim()
    .min(1, "Project Code is required")
    .max(50, "Project Code must be 50 characters or fewer")
    .regex(/^[A-Za-z0-9._-]+$/, "Use only letters, numbers, dots, dashes or underscores"),
  projectName: z
    .string()
    .trim()
    .min(1, "Project Name is required")
    .max(150, "Project Name must be 150 characters or fewer"),
  productionUrl: z
    .string()
    .trim()
    .min(1, "Production Health API URL is required")
    .max(2000, "URL must be 2000 characters or fewer")
    .refine((value) => parseHttpUrl(value) !== null, "Enter a valid http:// or https:// URL")
    .refine((value) => {
      const url = parseHttpUrl(value);
      return !url || !isBlockedHost(url.hostname);
    }, "This host is not allowed"),
  monitoringEnabled: z.boolean(),
  expectedHttpStatus: intField(
    "Expected HTTP Status",
    PROJECT_LIMITS.httpStatus.min,
    PROJECT_LIMITS.httpStatus.max,
    PROJECT_DEFAULTS.expectedHttpStatus,
  ),
  timeoutSeconds: intField(
    "Timeout",
    PROJECT_LIMITS.timeoutSeconds.min,
    PROJECT_LIMITS.timeoutSeconds.max,
    PROJECT_DEFAULTS.timeoutSeconds,
  ),
  description: z
    .string()
    .trim()
    .max(1000, "Description must be 1000 characters or fewer")
    .transform((value) => (value === "" ? null : value)),
});

export type ProjectInput = z.output<typeof projectSchema>;
export type ProjectField = keyof ProjectInput;
export type ProjectFieldErrors = Partial<Record<ProjectField, string>>;

export function projectFormValues(formData: FormData) {
  const text = (name: string) => String(formData.get(name) ?? "");
  return {
    projectCode: text("projectCode"),
    projectName: text("projectName"),
    productionUrl: text("productionUrl"),
    monitoringEnabled: formData.get("monitoringEnabled") === "on",
    expectedHttpStatus: text("expectedHttpStatus"),
    timeoutSeconds: text("timeoutSeconds"),
    description: text("description"),
  };
}

export type ProjectFormValues = ReturnType<typeof projectFormValues>;

export function validateProject(values: ProjectFormValues):
  | { ok: true; data: ProjectInput }
  | { ok: false; fieldErrors: ProjectFieldErrors } {
  const parsed = projectSchema.safeParse(values);
  if (parsed.success) return { ok: true, data: parsed.data };

  const fieldErrors: ProjectFieldErrors = {};
  for (const issue of parsed.error.issues) {
    const field = issue.path[0] as ProjectField | undefined;
    // First message per field wins.
    if (field && !fieldErrors[field]) fieldErrors[field] = issue.message;
  }
  return { ok: false, fieldErrors };
}
