import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
import { getPool } from "../db/pool";

export type DeviceCredentialStatus = "not_issued" | "active" | "rotation_required" | "revoked";

export interface DeviceEntity {
  id: string;
  device_code: string;
  serial_number?: string;
  vendor?: string;
  model?: string;
  communication_mode?: "static" | "mqtt" | "api";
  capability_profile_json?: Record<string, unknown>;
  auth_method?: string;
  mqtt_username?: string;
  credential_secret_fingerprint?: string;
  credential_status: DeviceCredentialStatus;
  credential_issued_at?: string;
  credential_rotated_at?: string;
  credential_revoked_at?: string;
  status: "active" | "inactive";
  last_seen_at?: string;
  firmware_version?: string;
  created_at: string;
  updated_at: string;
}

function nowIso() {
  return new Date().toISOString();
}

function makeCode(id: string) {
  return `d_${id.slice(0, 6)}`;
}

function mapDevice(row: any): DeviceEntity {
  return {
    id: row.id,
    device_code: row.device_code,
    serial_number: row.serial_number || undefined,
    vendor: row.vendor || undefined,
    model: row.model || undefined,
    communication_mode: row.communication_mode,
    capability_profile_json: row.capability_profile_json || {},
    auth_method: row.auth_method || undefined,
    mqtt_username: row.mqtt_username || undefined,
    credential_secret_fingerprint: row.credential_secret_fingerprint || undefined,
    credential_status: row.credential_status || "not_issued",
    credential_issued_at: row.credential_issued_at || undefined,
    credential_rotated_at: row.credential_rotated_at || undefined,
    credential_revoked_at: row.credential_revoked_at || undefined,
    status: row.status,
    last_seen_at: row.last_seen_at || undefined,
    firmware_version: row.firmware_version || undefined,
    created_at: row.created_at,
    updated_at: row.updated_at
  };
}

export async function createDevice(payload: {
  device_code?: string;
  serial_number?: string;
  vendor?: string;
  model?: string;
  communication_mode?: DeviceEntity["communication_mode"];
  capability_profile_json?: Record<string, unknown>;
  auth_method?: string;
  mqtt_username?: string;
  credential_secret_fingerprint?: string;
  credential_status?: DeviceCredentialStatus;
  credential_issued_at?: string;
  credential_rotated_at?: string;
  credential_revoked_at?: string;
  status?: DeviceEntity["status"];
  firmware_version?: string;
  last_seen_at?: string;
}): Promise<DeviceEntity> {
  const id = randomUUID();
  const now = nowIso();

  const { rows } = await getPool().query(
    `INSERT INTO devices (
      id,
      device_code,
      serial_number,
      vendor,
      model,
      communication_mode,
      capability_profile_json,
      auth_method,
      mqtt_username,
      credential_secret_fingerprint,
      credential_status,
      credential_issued_at,
      credential_rotated_at,
      credential_revoked_at,
      status,
      last_seen_at,
      firmware_version,
      created_at,
      updated_at
    ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19)
    RETURNING *`,
    [
      id,
      payload.device_code || makeCode(id),
      payload.serial_number,
      payload.vendor,
      payload.model,
      payload.communication_mode || "static",
      payload.capability_profile_json || {},
      payload.auth_method || "token",
      payload.mqtt_username || null,
      payload.credential_secret_fingerprint || null,
      payload.credential_status || "not_issued",
      payload.credential_issued_at || null,
      payload.credential_rotated_at || null,
      payload.credential_revoked_at || null,
      payload.status || "active",
      payload.last_seen_at || null,
      payload.firmware_version,
      now,
      now
    ]
  );

  return mapDevice(rows[0]);
}

export async function listDevices(): Promise<DeviceEntity[]> {
  const { rows } = await getPool().query("SELECT * FROM devices ORDER BY created_at DESC");
  return rows.map(mapDevice);
}

export async function getDeviceById(id: string): Promise<DeviceEntity | null> {
  const { rows } = await getPool().query("SELECT * FROM devices WHERE id = $1", [id]);
  return rows[0] ? mapDevice(rows[0]) : null;
}

export async function getDeviceBySerialNumber(serialNumber: string): Promise<DeviceEntity | null> {
  const { rows } = await getPool().query("SELECT * FROM devices WHERE serial_number = $1 ORDER BY created_at DESC LIMIT 1", [
    serialNumber
  ]);
  return rows[0] ? mapDevice(rows[0]) : null;
}

export async function patchDevice(id: string, patch: Partial<DeviceEntity>): Promise<DeviceEntity> {
  const existing = await getDeviceById(id);
  if (!existing) {
    throw new Error("DEVICE_NOT_FOUND");
  }

  const merged = { ...existing, ...patch, updated_at: nowIso() };

  const { rows } = await getPool().query(
    `UPDATE devices
      SET device_code = $2,
          serial_number = $3,
          vendor = $4,
          model = $5,
          communication_mode = $6,
          capability_profile_json = $7,
          auth_method = $8,
          mqtt_username = $9,
          credential_secret_fingerprint = $10,
          credential_status = $11,
          credential_issued_at = $12,
          credential_rotated_at = $13,
          credential_revoked_at = $14,
          status = $15,
          firmware_version = $16,
          last_seen_at = $17,
          updated_at = $18
      WHERE id = $1
      RETURNING *`,
    [
      id,
      merged.device_code,
      merged.serial_number,
      merged.vendor,
      merged.model,
      merged.communication_mode || "static",
      merged.capability_profile_json || {},
      merged.auth_method,
      merged.mqtt_username || null,
      merged.credential_secret_fingerprint || null,
      merged.credential_status || "not_issued",
      merged.credential_issued_at || null,
      merged.credential_rotated_at || null,
      merged.credential_revoked_at || null,
      merged.status,
      merged.firmware_version,
      merged.last_seen_at || null,
      merged.updated_at
    ]
  );

  return mapDevice(rows[0]);
}

function fingerprintSecret(secret: string) {
  return createHash("sha256").update(secret).digest("hex");
}

function safeEqualHex(a: string, b: string) {
  const left = Buffer.from(a, "hex");
  const right = Buffer.from(b, "hex");
  return left.length === right.length && timingSafeEqual(left, right);
}

function normalizeMqttUsername(device: DeviceEntity) {
  return device.id;
}

export async function rotateDeviceMqttCredential(id: string): Promise<{
  device: DeviceEntity;
  username: string;
  password: string;
}> {
  const existing = await getDeviceById(id);
  if (!existing) {
    throw new Error("DEVICE_NOT_FOUND");
  }

  const password = randomBytes(24).toString("base64url");
  const username = normalizeMqttUsername(existing);
  const now = nowIso();
  const updated = await patchDevice(id, {
    communication_mode: "mqtt",
    auth_method: "mqtt_username_password",
    mqtt_username: username,
    credential_secret_fingerprint: fingerprintSecret(password),
    credential_status: "active",
    credential_issued_at: existing.credential_issued_at || now,
    credential_rotated_at: now,
    credential_revoked_at: undefined
  });

  return { device: updated, username, password };
}

export async function verifyDeviceSecret(id: string, secret: string): Promise<DeviceEntity | null> {
  const device = await getDeviceById(id);
  if (!device || device.credential_status !== "active" || !device.credential_secret_fingerprint) {
    return null;
  }

  const candidate = fingerprintSecret(secret);
  return safeEqualHex(candidate, device.credential_secret_fingerprint) ? device : null;
}

export function toDevicePayload(device: DeviceEntity) {
  const { credential_secret_fingerprint, ...payload } = device;
  return { ...payload };
}
