/**
 * Smoke: v7 visibility round-trip.
 *
 * Publishes two virtual Llms via the registrar — one explicitly public,
 * one explicitly private — and verifies the GET /api/v1/llms response
 * carries the visibility + ownerId fields end-to-end. The cross-user
 * filter (private rows hidden from non-owner non-admin) is fully
 * covered by mcpd's visibility-filter unit tests; smoke only proves
 * the new fields make the round-trip from registrar → mcpd → list
 * payload without dropping or being defaulted away.
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import http from 'node:http';
import https from 'node:https';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
  VirtualLlmRegistrar,
  type RegistrarPublishedProvider,
} from '../../src/providers/registrar.js';
import type { LlmProvider, CompletionResult } from '../../src/providers/types.js';

const MCPD_URL = process.env.MCPD_URL ?? 'https://mcpctl.ad.itaz.eu';
const SUFFIX = Date.now().toString(36);
const PUBLIC_NAME = `smoke-vis-public-${SUFFIX}`;
const PRIVATE_NAME = `smoke-vis-private-${SUFFIX}`;

function makeFakeProvider(name: string): LlmProvider {
  return {
    name,
    async complete(): Promise<CompletionResult> {
      return {
        content: 'ok',
        toolCalls: [],
        usage: { promptTokens: 1, completionTokens: 1, totalTokens: 2 },
        finishReason: 'stop',
      };
    },
    async listModels() { return []; },
    async isAvailable() { return true; },
  };
}

function healthz(url: string, timeoutMs = 5000): Promise<boolean> {
  return new Promise((resolve) => {
    const parsed = new URL(`${url.replace(/\/$/, '')}/healthz`);
    const driver = parsed.protocol === 'https:' ? https : http;
    const req = driver.get(
      {
        hostname: parsed.hostname,
        port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
        path: parsed.pathname,
        timeout: timeoutMs,
      },
      (res) => { resolve((res.statusCode ?? 500) < 500); res.resume(); },
    );
    req.on('error', () => resolve(false));
    req.on('timeout', () => { req.destroy(); resolve(false); });
  });
}

function readToken(): string | null {
  try {
    const home = process.env.HOME ?? '';
    const path = `${home}/.mcpctl/credentials`;
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const fs = require('node:fs') as typeof import('node:fs');
    if (!fs.existsSync(path)) return null;
    const raw = fs.readFileSync(path, 'utf-8');
    const parsed = JSON.parse(raw) as { token?: string };
    return parsed.token ?? null;
  } catch {
    return null;
  }
}

interface HttpResponse { status: number; body: string }

function httpRequest(method: string, urlStr: string): Promise<HttpResponse> {
  return new Promise((resolve, reject) => {
    const tokenRaw = readToken();
    const parsed = new URL(urlStr);
    const driver = parsed.protocol === 'https:' ? https : http;
    const headers: Record<string, string> = {
      Accept: 'application/json',
      ...(tokenRaw !== null ? { Authorization: `Bearer ${tokenRaw}` } : {}),
    };
    const req = driver.request({
      hostname: parsed.hostname,
      port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
      path: parsed.pathname + parsed.search,
      method,
      headers,
      timeout: 30_000,
    }, (res) => {
      const chunks: Buffer[] = [];
      res.on('data', (c: Buffer) => chunks.push(c));
      res.on('end', () => {
        resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf-8') });
      });
    });
    req.on('error', reject);
    req.on('timeout', () => { req.destroy(); reject(new Error(`httpRequest timeout: ${method} ${urlStr}`)); });
    req.end();
  });
}

let mcpdUp = false;
let registrar: VirtualLlmRegistrar | null = null;
let tempDir: string;

interface LlmListRow {
  id: string;
  name: string;
  visibility?: 'public' | 'private';
  ownerId?: string | null;
}

describe('virtual-LLM smoke — visibility (v7)', () => {
  beforeAll(async () => {
    mcpdUp = await healthz(MCPD_URL);
    if (!mcpdUp) {
      // eslint-disable-next-line no-console
      console.warn(`\n  ○ visibility smoke: skipped — ${MCPD_URL}/healthz unreachable.\n`);
      return;
    }
    if (readToken() === null) {
      mcpdUp = false;
      // eslint-disable-next-line no-console
      console.warn('\n  ○ visibility smoke: skipped — no ~/.mcpctl/credentials.\n');
      return;
    }
    tempDir = mkdtempSync(join(tmpdir(), 'mcpctl-vis-smoke-'));
  }, 20_000);

  afterAll(async () => {
    if (registrar !== null) registrar.stop();
    if (tempDir !== undefined) rmSync(tempDir, { recursive: true, force: true });
    if (mcpdUp) {
      const list = await httpRequest('GET', `${MCPD_URL}/api/v1/llms`);
      if (list.status === 200) {
        const rows = JSON.parse(list.body) as LlmListRow[];
        for (const target of [PUBLIC_NAME, PRIVATE_NAME]) {
          const row = rows.find((r) => r.name === target);
          if (row !== undefined) {
            await httpRequest('DELETE', `${MCPD_URL}/api/v1/llms/${row.id}`);
          }
        }
      }
    }
  });

  it('publishes one public + one private virtual Llm and the list payload reflects both', async () => {
    if (!mcpdUp) return;
    const token = readToken();
    if (token === null) return;
    const published: RegistrarPublishedProvider[] = [
      { provider: makeFakeProvider(PUBLIC_NAME),  type: 'openai', model: 'fake-vis', tier: 'fast', visibility: 'public'  },
      { provider: makeFakeProvider(PRIVATE_NAME), type: 'openai', model: 'fake-vis', tier: 'fast', visibility: 'private' },
    ];
    registrar = new VirtualLlmRegistrar({
      mcpdUrl: MCPD_URL,
      token,
      publishedProviders: published,
      sessionFilePath: join(tempDir, 'session'),
      log: { info: () => {}, warn: () => {}, error: () => {} },
      heartbeatIntervalMs: 60_000,
    });
    await registrar.start();
    expect(registrar.getSessionId()).not.toBeNull();
    await new Promise((r) => setTimeout(r, 400));

    const res = await httpRequest('GET', `${MCPD_URL}/api/v1/llms`);
    expect(res.status).toBe(200);
    const rows = JSON.parse(res.body) as LlmListRow[];

    const pub = rows.find((r) => r.name === PUBLIC_NAME);
    expect(pub, `${PUBLIC_NAME} must be visible to its owner`).toBeDefined();
    expect(pub!.visibility).toBe('public');
    // ownerId is the auth principal that ran register; non-empty proves
    // mcpd actually stamped it on the row (otherwise the v7 register
    // path would have left it NULL = legacy public).
    expect(typeof pub!.ownerId).toBe('string');
    expect((pub!.ownerId ?? '').length).toBeGreaterThan(0);

    const priv = rows.find((r) => r.name === PRIVATE_NAME);
    expect(priv, `${PRIVATE_NAME} must be visible to its owner (visibility filter is owner-bypass)`).toBeDefined();
    expect(priv!.visibility).toBe('private');
    expect(typeof priv!.ownerId).toBe('string');
    expect((priv!.ownerId ?? '').length).toBeGreaterThan(0);

    // Same publisher, same session — both rows must share the same owner.
    expect(priv!.ownerId).toBe(pub!.ownerId);
  }, 30_000);

  it('GET /api/v1/llms/<name> returns the row to its owner without 404', async () => {
    if (!mcpdUp) return;
    // Owner is calling — visibility filter must let the row through. A
    // 404 here would indicate the service-layer filter is wrongly hiding
    // it from the very user who created it.
    const res = await httpRequest('GET', `${MCPD_URL}/api/v1/llms/${PRIVATE_NAME}`);
    expect(res.status).toBe(200);
    const row = JSON.parse(res.body) as LlmListRow;
    expect(row.name).toBe(PRIVATE_NAME);
    expect(row.visibility).toBe('private');
  }, 30_000);
});
