import { randomUUID } from "node:crypto";
import { readFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

import pg from "pg";

import { ARUSNAVI_B2_MODEL_PROFILE } from "../../../packages/arusnavi-b2-adapter/src/index.mjs";

const { Pool } = pg;
const serviceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");

export class PostgresDeviceRepository {
  constructor({ databaseUrl, poolSize = 10 } = {}) {
    if (typeof databaseUrl !== "string" || databaseUrl.trim() === "") {
      throw new TypeError("device_database_url_required");
    }
    this.pool = new Pool({
      connectionString: databaseUrl,
      max: normalizePoolSize(poolSize),
    });
  }

  async migrate() {
    const sql = await readFile(
      resolve(serviceRoot, "migrations/001_device_plane_foundation.sql"),
      "utf8",
    );
    await this.pool.query(sql);
    await this.pool.query(
      `insert into device_model_profiles (
        profile_ref,
        schema_version,
        vendor,
        model,
        device_type,
        protocol,
        profile
      ) values ($1, $2, $3, $4, $5, $6, $7::jsonb)
      on conflict (profile_ref) do update set
        schema_version = excluded.schema_version,
        profile = excluded.profile,
        updated_at = now()`,
      [
        ARUSNAVI_B2_MODEL_PROFILE.profileRef,
        ARUSNAVI_B2_MODEL_PROFILE.schemaVersion,
        ARUSNAVI_B2_MODEL_PROFILE.vendor,
        ARUSNAVI_B2_MODEL_PROFILE.model,
        ARUSNAVI_B2_MODEL_PROFILE.deviceType,
        ARUSNAVI_B2_MODEL_PROFILE.protocol,
        JSON.stringify(ARUSNAVI_B2_MODEL_PROFILE),
      ],
    );
  }

  async health() {
    await this.pool.query("select 1");
    return "ready";
  }

  async upsertQuarantineDiscovery({ identifierDigest, safeView }) {
    const result = await this.pool.query(
      `insert into device_discoveries (
        id,
        identifier_kind,
        identifier_digest,
        identifier_masked,
        model_profile_ref,
        protocol,
        lifecycle_state,
        first_observed_at,
        last_observed_at,
        evidence
      ) values ($1, $2, $3, $4, $5, $6, 'quarantine', $7, $7, $8::jsonb)
      on conflict (identifier_kind, identifier_digest, model_profile_ref)
      do update set
        last_observed_at = greatest(
          device_discoveries.last_observed_at,
          excluded.last_observed_at
        ),
        evidence = excluded.evidence,
        updated_at = now()
      returning id, lifecycle_state, model_profile_ref, protocol,
        identifier_kind, identifier_masked, first_observed_at,
        last_observed_at, (xmax = 0) as created`,
      [
        randomUUID(),
        safeView.identifier.kind,
        identifierDigest,
        safeView.identifier.masked,
        safeView.modelProfileRef,
        safeView.protocol,
        safeView.observedAt,
        JSON.stringify(safeView.evidence),
      ],
    );
    const row = result.rows[0];
    return {
      created: row.created === true,
      value: {
        schemaVersion: "nodedc.device.discovery-view.v1",
        discoveryRef: `discovery:${row.id}`,
        modelProfileRef: row.model_profile_ref,
        protocol: row.protocol,
        observedAt: new Date(row.last_observed_at).toISOString(),
        lifecycleState: row.lifecycle_state,
        identifier: {
          kind: row.identifier_kind,
          masked: row.identifier_masked,
        },
        evidence: safeView.evidence,
        commandTransport: "disabled",
      },
    };
  }

  async close() {
    await this.pool.end();
  }
}

function normalizePoolSize(value) {
  const parsed = Number(value);
  if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 50) {
    throw new TypeError("device_database_pool_size_invalid");
  }
  return parsed;
}
