import { randomUUID } from "node:crypto";
import { execFile } from "node:child_process";
import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { z } from "zod";
import type { AppConfig } from "./config.js";

const execFileAsync = promisify(execFile);

export const networkModeSchema = z.enum([
  "disabled",
  "starting",
  "dual_radio",
  "built_in_upstream",
  "probing_built_in",
  "offline_hotspot",
  "degraded",
  "error"
]);
export type NetworkMode = z.infer<typeof networkModeSchema>;

const adapterSchema = z.object({
  interface: z.string().max(64),
  available: z.boolean(),
  connected: z.boolean(),
  connection: z.string().max(128).nullable(),
  address: z.string().max(128).nullable()
});

export const networkStatusSchema = z.object({
  enabled: z.boolean(),
  mode: networkModeSchema,
  message: z.string().max(500),
  updatedAt: z.string(),
  lastTransitionAt: z.string(),
  probeDeadline: z.string().nullable(),
  lastFailure: z.string().max(500).nullable(),
  connectivity: z.enum(["full", "limited", "portal", "none", "unknown"]),
  hotspotSsid: z.string().max(32).nullable(),
  hotspot: adapterSchema,
  upstream: adapterSchema
});

export type NetworkStatus = z.infer<typeof networkStatusSchema> & { supported: boolean };
export const networkActionSchema = z.enum(["retry-upstream", "restore-hotspot"]);
export type NetworkAction = z.infer<typeof networkActionSchema>;

export const hotspotConfigurationSchema = z.object({
  ssid: z
    .string()
    .trim()
    .min(1)
    .max(32)
    .regex(/^[a-zA-Z0-9_. -]+$/, "Use letters, numbers, spaces, dots, hyphens, or underscores"),
  password: z.string().min(8).max(63).regex(/^[\x20-\x7e]+$/, "Use printable characters only")
});
export type HotspotConfiguration = z.infer<typeof hotspotConfigurationSchema>;

const hotspotConfigurationRequestSchema = hotspotConfigurationSchema.extend({
  id: z.string().uuid(),
  requestedAt: z.string(),
  actorUserId: z.number().int().positive()
});
export type HotspotConfigurationRequest = z.infer<typeof hotspotConfigurationRequestSchema>;

const commandSchema = z.object({
  id: z.string().uuid(),
  action: networkActionSchema,
  requestedAt: z.string(),
  actorUserId: z.number().int().positive()
});
export type NetworkCommand = z.infer<typeof commandSchema>;

async function atomicJsonWrite(path: string, value: unknown, mode = 0o640): Promise<void> {
  const temporary = join(dirname(path), `.${randomUUID()}.tmp`);
  await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode });
  await chmod(temporary, mode);
  await rename(temporary, path);
}

export async function readNetworkStatus(config: AppConfig): Promise<NetworkStatus> {
  try {
    return { ...networkStatusSchema.parse(JSON.parse(await readFile(config.network.statusPath, "utf8"))), supported: true };
  } catch {
    if (!config.network.enabled) {
      return unsupportedStatus("Set the hotspot name and password below to enable automatic network management.");
    }
    return {
      ...unsupportedStatus("The network controller is starting or its status is unavailable."),
      enabled: true,
      mode: "starting"
    };
  }
}

export async function requestNetworkAction(
  config: AppConfig,
  action: NetworkAction,
  actorUserId: number
): Promise<NetworkCommand> {
  if (!(await readNetworkStatus(config)).enabled) throw new Error("NETWORK_MANAGER_DISABLED");
  const command = commandSchema.parse({ id: randomUUID(), action, requestedAt: new Date().toISOString(), actorUserId });
  await atomicJsonWrite(config.network.commandPath, command);
  return command;
}

export async function requestHotspotConfiguration(
  config: AppConfig,
  input: HotspotConfiguration,
  actorUserId: number,
  trigger: () => Promise<void> = triggerHotspotConfiguration
): Promise<HotspotConfigurationRequest> {
  const request = hotspotConfigurationRequestSchema.parse({
    ...input,
    id: randomUUID(),
    requestedAt: new Date().toISOString(),
    actorUserId
  });
  await writeFile(config.network.configurationRequestPath, `${JSON.stringify(request)}\n`, { flag: "wx", mode: 0o600 });
  try {
    await trigger();
  } catch (error) {
    await rm(config.network.configurationRequestPath, { force: true });
    throw error;
  }
  return request;
}

async function triggerHotspotConfiguration(): Promise<void> {
  await execFileAsync(
    "/usr/bin/sudo",
    ["-n", "/usr/bin/systemctl", "start", "--no-block", "pi-car-companion-network-configure.service"],
    { timeout: 10_000, maxBuffer: 16 * 1024 }
  );
}

export async function consumeHotspotConfigurationRequest(path: string): Promise<HotspotConfigurationRequest> {
  const content = await readFile(path, "utf8");
  await rm(path, { force: true });
  return hotspotConfigurationRequestSchema.parse(JSON.parse(content));
}

export async function consumeNetworkCommand(path: string): Promise<NetworkCommand | null> {
  try {
    const parsed = commandSchema.parse(JSON.parse(await readFile(path, "utf8")));
    await writeFile(path, "", { mode: 0o640 });
    return parsed;
  } catch {
    return null;
  }
}

function unsupportedStatus(message: string): NetworkStatus {
  const epoch = new Date(0).toISOString();
  const adapter = { interface: "unconfigured", available: false, connected: false, connection: null, address: null };
  return {
    supported: false,
    enabled: false,
    mode: "disabled",
    message,
    updatedAt: epoch,
    lastTransitionAt: epoch,
    probeDeadline: null,
    lastFailure: null,
    connectivity: "unknown",
    hotspotSsid: null,
    hotspot: adapter,
    upstream: adapter
  };
}

export async function writeNetworkStatus(path: string, status: z.infer<typeof networkStatusSchema>): Promise<void> {
  await atomicJsonWrite(path, networkStatusSchema.parse(status));
  await chmod(path, 0o644);
}
