import { randomUUID } from "node:crypto";
import { getPool } from "../db/pool";

export type CatalogStatus = "active" | "inactive";
export type CatalogCommunicationMode = "static" | "mqtt" | "api";
export type CatalogQrMode = "static" | "dynamic_mqtt" | "dynamic_api";

export interface SoundboxVendorEntity {
  id: string;
  vendor_code: string;
  name: string;
  country?: string;
  support_contact?: string;
  support_pic_name?: string;
  support_email?: string;
  support_phone?: string;
  support_notes?: string;
  status: CatalogStatus;
  created_at: string;
  updated_at: string;
}

export interface SoundboxModelEntity {
  id: string;
  vendor_id: string;
  vendor_code?: string;
  vendor_name?: string;
  model_code: string;
  name: string;
  communication_mode: CatalogCommunicationMode;
  screen_flag: boolean;
  qr_mode: CatalogQrMode;
  mqtt_payload_profile?: string;
  thumbnail_url?: string;
  capability_template_json: Record<string, unknown>;
  status: CatalogStatus;
  created_at: string;
  updated_at: string;
}

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

function slugCode(value: string) {
  const slug = value
    .trim()
    .toUpperCase()
    .replace(/[^A-Z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "");
  return slug || "MODEL";
}

async function generateModelCode(vendorId: string, modelName: string) {
  const vendor = await getSoundboxVendorById(vendorId);
  const vendorCode = slugCode(vendor?.vendor_code || "VENDOR");
  const baseCode = `${vendorCode}_${slugCode(modelName)}`.slice(0, 80);
  let candidate = baseCode;
  let suffix = 2;

  while (true) {
    const { rows } = await getPool().query(
      "SELECT 1 FROM soundbox_models WHERE vendor_id = $1 AND model_code = $2 LIMIT 1",
      [vendorId, candidate]
    );
    if (!rows.length) {
      return candidate;
    }
    candidate = `${baseCode}_${suffix++}`.slice(0, 96);
  }
}

function mapVendor(row: any): SoundboxVendorEntity {
  return {
    id: row.id,
    vendor_code: row.vendor_code,
    name: row.name,
    country: row.country || undefined,
    support_contact: row.support_contact || undefined,
    support_pic_name: row.support_pic_name || undefined,
    support_email: row.support_email || undefined,
    support_phone: row.support_phone || undefined,
    support_notes: row.support_notes || undefined,
    status: row.status,
    created_at: row.created_at,
    updated_at: row.updated_at
  };
}

function mapModel(row: any): SoundboxModelEntity {
  return {
    id: row.id,
    vendor_id: row.vendor_id,
    vendor_code: row.vendor_code || undefined,
    vendor_name: row.vendor_name || undefined,
    model_code: row.model_code,
    name: row.name,
    communication_mode: row.communication_mode,
    screen_flag: Boolean(row.screen_flag),
    qr_mode: row.qr_mode,
    mqtt_payload_profile: row.mqtt_payload_profile || undefined,
    thumbnail_url: row.thumbnail_url || undefined,
    capability_template_json: row.capability_template_json || {},
    status: row.status,
    created_at: row.created_at,
    updated_at: row.updated_at
  };
}

export async function listSoundboxVendors(filter?: { status?: CatalogStatus }): Promise<SoundboxVendorEntity[]> {
  const clauses: string[] = [];
  const params: unknown[] = [];
  if (filter?.status) {
    clauses.push("status = $1");
    params.push(filter.status);
  }
  const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
  const { rows } = await getPool().query(`SELECT * FROM soundbox_vendors ${where} ORDER BY name ASC`, params);
  return rows.map(mapVendor);
}

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

export async function getSoundboxVendorByCode(vendorCode: string): Promise<SoundboxVendorEntity | null> {
  const { rows } = await getPool().query("SELECT * FROM soundbox_vendors WHERE vendor_code = $1", [vendorCode]);
  return rows.length ? mapVendor(rows[0]) : null;
}

export async function createSoundboxVendor(payload: {
  vendor_code: string;
  name: string;
  country?: string;
  support_contact?: string;
  support_pic_name?: string;
  support_email?: string;
  support_phone?: string;
  support_notes?: string;
  status?: CatalogStatus;
}): Promise<SoundboxVendorEntity> {
  const id = randomUUID();
  const now = nowIso();
  const { rows } = await getPool().query(
    `INSERT INTO soundbox_vendors (
      id,
      vendor_code,
      name,
      country,
      support_contact,
      support_pic_name,
      support_email,
      support_phone,
      support_notes,
      status,
      created_at,
      updated_at
    )
     VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
     RETURNING *`,
    [
      id,
      payload.vendor_code,
      payload.name,
      payload.country || null,
      payload.support_contact || null,
      payload.support_pic_name || null,
      payload.support_email || null,
      payload.support_phone || null,
      payload.support_notes || null,
      payload.status || "active",
      now,
      now
    ]
  );
  return mapVendor(rows[0]);
}

export async function patchSoundboxVendor(
  id: string,
  patch: Partial<Omit<SoundboxVendorEntity, "id" | "created_at" | "updated_at">>
): Promise<SoundboxVendorEntity> {
  const existing = await getSoundboxVendorById(id);
  if (!existing) {
    throw new Error("SOUNDBOX_VENDOR_NOT_FOUND");
  }
  const merged = { ...existing, ...patch, updated_at: nowIso() };
  const { rows } = await getPool().query(
    `UPDATE soundbox_vendors
       SET vendor_code = $2,
           name = $3,
           country = $4,
           support_contact = $5,
           support_pic_name = $6,
           support_email = $7,
           support_phone = $8,
           support_notes = $9,
           status = $10,
           updated_at = $11
     WHERE id = $1
     RETURNING *`,
    [
      id,
      merged.vendor_code,
      merged.name,
      merged.country || null,
      merged.support_contact || null,
      merged.support_pic_name || null,
      merged.support_email || null,
      merged.support_phone || null,
      merged.support_notes || null,
      merged.status,
      merged.updated_at
    ]
  );
  return mapVendor(rows[0]);
}

export async function deleteSoundboxVendor(id: string): Promise<SoundboxVendorEntity> {
  const existing = await getSoundboxVendorById(id);
  if (!existing) {
    throw new Error("SOUNDBOX_VENDOR_NOT_FOUND");
  }
  const { rows: modelRows } = await getPool().query("SELECT COUNT(*)::int AS count FROM soundbox_models WHERE vendor_id = $1", [id]);
  if ((modelRows[0]?.count || 0) > 0) {
    throw new Error("SOUNDBOX_VENDOR_HAS_MODELS");
  }
  await getPool().query("DELETE FROM soundbox_vendors WHERE id = $1", [id]);
  return existing;
}

export async function listSoundboxModels(filter?: {
  vendor_id?: string;
  vendor_code?: string;
  status?: CatalogStatus;
}): Promise<SoundboxModelEntity[]> {
  const clauses: string[] = [];
  const params: unknown[] = [];
  let i = 1;
  if (filter?.vendor_id) {
    clauses.push(`m.vendor_id = $${i++}`);
    params.push(filter.vendor_id);
  }
  if (filter?.vendor_code) {
    clauses.push(`v.vendor_code = $${i++}`);
    params.push(filter.vendor_code);
  }
  if (filter?.status) {
    clauses.push(`m.status = $${i++}`);
    params.push(filter.status);
  }
  const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
  const { rows } = await getPool().query(
    `SELECT m.*, v.vendor_code, v.name AS vendor_name
       FROM soundbox_models m
       JOIN soundbox_vendors v ON v.id = m.vendor_id
       ${where}
      ORDER BY v.name ASC, m.model_code ASC`,
    params
  );
  return rows.map(mapModel);
}

export async function getSoundboxModelById(id: string): Promise<SoundboxModelEntity | null> {
  const { rows } = await getPool().query(
    `SELECT m.*, v.vendor_code, v.name AS vendor_name
       FROM soundbox_models m
       JOIN soundbox_vendors v ON v.id = m.vendor_id
      WHERE m.id = $1`,
    [id]
  );
  return rows.length ? mapModel(rows[0]) : null;
}

export async function createSoundboxModel(payload: {
  vendor_id: string;
  model_code?: string;
  name: string;
  communication_mode?: CatalogCommunicationMode;
  screen_flag?: boolean;
  qr_mode?: CatalogQrMode;
  mqtt_payload_profile?: string;
  thumbnail_url?: string;
  capability_template_json?: Record<string, unknown>;
  status?: CatalogStatus;
}): Promise<SoundboxModelEntity> {
  const id = randomUUID();
  const now = nowIso();
  const modelCode = payload.model_code?.trim() || await generateModelCode(payload.vendor_id, payload.name);
  const { rows } = await getPool().query(
    `INSERT INTO soundbox_models (
      id, vendor_id, model_code, name, communication_mode, screen_flag, qr_mode,
      mqtt_payload_profile, thumbnail_url, capability_template_json, status, created_at, updated_at
    )
    VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
    RETURNING *`,
    [
      id,
      payload.vendor_id,
      modelCode,
      payload.name,
      payload.communication_mode || "mqtt",
      payload.screen_flag ?? false,
      payload.qr_mode || "static",
      payload.mqtt_payload_profile || null,
      payload.thumbnail_url || null,
      payload.capability_template_json || {},
      payload.status || "active",
      now,
      now
    ]
  );
  const created = await getSoundboxModelById(rows[0].id);
  return created || mapModel(rows[0]);
}

export async function patchSoundboxModel(
  id: string,
  patch: Partial<Omit<SoundboxModelEntity, "id" | "created_at" | "updated_at" | "vendor_code" | "vendor_name">>
): Promise<SoundboxModelEntity> {
  const existing = await getSoundboxModelById(id);
  if (!existing) {
    throw new Error("SOUNDBOX_MODEL_NOT_FOUND");
  }
  const merged = { ...existing, ...patch, updated_at: nowIso() };
  const { rows } = await getPool().query(
    `UPDATE soundbox_models
       SET vendor_id = $2,
           model_code = $3,
           name = $4,
           communication_mode = $5,
           screen_flag = $6,
           qr_mode = $7,
           mqtt_payload_profile = $8,
           thumbnail_url = $9,
           capability_template_json = $10,
           status = $11,
           updated_at = $12
     WHERE id = $1
     RETURNING *`,
    [
      id,
      merged.vendor_id,
      merged.model_code,
      merged.name,
      merged.communication_mode,
      merged.screen_flag,
      merged.qr_mode,
      merged.mqtt_payload_profile || null,
      merged.thumbnail_url || null,
      merged.capability_template_json || {},
      merged.status,
      merged.updated_at
    ]
  );
  const updated = await getSoundboxModelById(rows[0].id);
  return updated || mapModel(rows[0]);
}

export async function deleteSoundboxModel(id: string): Promise<SoundboxModelEntity> {
  const existing = await getSoundboxModelById(id);
  if (!existing) {
    throw new Error("SOUNDBOX_MODEL_NOT_FOUND");
  }
  await getPool().query("DELETE FROM soundbox_models WHERE id = $1", [id]);
  return existing;
}

export function toSoundboxVendorPayload(vendor: SoundboxVendorEntity) {
  return { ...vendor };
}

export function toSoundboxModelPayload(model: SoundboxModelEntity) {
  return { ...model };
}
