import { existsSync, readFileSync, renameSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { z } from "zod";
import { EvidenceTrustSchema } from "./evidence";

export const DEFAULT_BIND_HOST = "127.0.0.1";
export const DEFAULT_CONTROLLER_PORT = 8787;

export class ControllerFatalError extends Error {
  constructor(
    public readonly code: string,
    message: string,
  ) {
    super(message);
    this.name = "ControllerFatalError";
  }
}

export function configDir(): string {
  return process.env.OVERDECK_CONFIG_DIR ?? join(homedir(), ".config", "overdeck");
}

export function configFile(): string {
  return join(configDir(), "controller.toml");
}

export function dataDir(config?: Pick<ControllerConfig, "dataDir">): string {
  return config?.dataDir ?? join(configDir(), "controller");
}

const NotifyConfigSchema = z.object({
  mode: z.enum(["journal", "desktop", "both"]).default("both"),
}).default({ mode: "both" });

export type NotifyConfig = z.infer<typeof NotifyConfigSchema>;

const DeployWatcherConfigSchema = z.object({
  enabled: z.boolean().default(false),
  deployDir: z.string().optional(),
  repoRoot: z.string().optional(),
}).default({ enabled: false });

export type DeployWatcherConfig = z.infer<typeof DeployWatcherConfigSchema>;

const ConfigSchema = z.object({
  bindHost: z.literal(DEFAULT_BIND_HOST).default(DEFAULT_BIND_HOST),
  port: z.number().int().positive().default(DEFAULT_CONTROLLER_PORT),
  enabled: z.boolean().default(true),
  dataDir: z.string().optional(),
  landRepositoryRoots: z.array(z.string()).default([]),
  notify: NotifyConfigSchema,
  deliveryEvidence: EvidenceTrustSchema.optional(),
  deployWatcher: DeployWatcherConfigSchema,
}).strict();
export type ControllerConfig = z.infer<typeof ConfigSchema>;

export interface ConfigIncident {
  kind: "config-invalid-toml" | "config-invalid-shape" | "config-unreadable";
  detail: string;
}

export interface ConfigLoadResult {
  config: ControllerConfig;
  degraded: boolean;
  incident?: ConfigIncident;
  /** Legitimate disabled state — local execution allowed by policy. */
  legitimatelyDisabled: boolean;
  /** Explicit operator override via BUILD_REMOTE_LOCAL_FALLBACK=1. */
  localFallbackOverride: boolean;
}

export function defaultConfig(): ControllerConfig {
  return ConfigSchema.parse({});
}

function parseTomlFromFile(path: string): { ok: true; value: unknown } | { ok: false; incident: ConfigIncident } {
  let raw: string;
  try {
    raw = readFileSync(path, "utf8");
  } catch (err) {
    return {
      ok: false,
      incident: {
        kind: "config-unreadable",
        detail: `cannot read config at ${path}: ${(err as Error).message}`,
      },
    };
  }

  try {
    return { ok: true, value: Bun.TOML.parse(raw) };
  } catch (err) {
    return {
      ok: false,
      incident: {
        kind: "config-invalid-toml",
        detail: `config at ${path} is not valid TOML: ${(err as Error).message}`,
      },
    };
  }
}

function preserveInvalidConfig(path: string): string {
  let epoch = Date.now();
  let preservedPath = `${path}.invalid-${epoch}`;
  while (existsSync(preservedPath)) {
    epoch += 1;
    preservedPath = `${path}.invalid-${epoch}`;
  }
  renameSync(path, preservedPath);
  return preservedPath;
}

function invalidConfigResult(
  path: string,
  incident: ConfigIncident,
  localFallbackOverride: boolean,
): ConfigLoadResult {
  const preservedPath = preserveInvalidConfig(path);
  const overrideDetail = localFallbackOverride
    ? "; BUILD_REMOTE_LOCAL_FALLBACK=1 honored"
    : "";
  return {
    config: defaultConfig(),
    degraded: true,
    incident: {
      ...incident,
      detail: `${incident.detail}; preserved as ${preservedPath}${overrideDetail}`,
    },
    legitimatelyDisabled: false,
    localFallbackOverride,
  };
}

export function loadConfigResult(path: string = configFile()): ConfigLoadResult {
  const localFallbackOverride = process.env.BUILD_REMOTE_LOCAL_FALLBACK === "1";

  if (!existsSync(path)) {
    const config = defaultConfig();
    return {
      config,
      degraded: false,
      legitimatelyDisabled: !config.enabled,
      localFallbackOverride,
    };
  }

  const parsed = parseTomlFromFile(path);
  if (!parsed.ok) {
    return invalidConfigResult(path, parsed.incident, localFallbackOverride);
  }

  const validated = ConfigSchema.safeParse(parsed.value);
  if (!validated.success) {
    return invalidConfigResult(
      path,
      {
        kind: "config-invalid-shape",
        detail: `config at ${path} failed validation: ${validated.error.message}`,
      },
      localFallbackOverride,
    );
  }

  return {
    config: validated.data,
    degraded: false,
    legitimatelyDisabled: !validated.data.enabled,
    localFallbackOverride,
  };
}

/** Fail-fast loader for startup paths that require a readable config file. */
export function loadConfig(): ControllerConfig {
  const path = configFile();
  if (!existsSync(path)) {
    return defaultConfig();
  }

  const result = loadConfigResult(path);
  if (result.degraded && result.incident) {
    throw new ControllerFatalError(
      result.incident.kind.toUpperCase().replace(/-/g, "_"),
      result.incident.detail,
    );
  }
  return result.config;
}

export function isLocalFallbackAuthorized(result: ConfigLoadResult): boolean {
  if (result.legitimatelyDisabled) {
    return true;
  }
  if (result.localFallbackOverride) {
    return true;
  }
  return false;
}
