// ── Addon Smoke Test Module ──
// Tests configured stream addons and exposes results via API endpoints.

export type AddonState = 'Ready' | 'NoResults' | 'NeedsConfig' | 'Degraded' | 'Failed' | 'Disabled';

export interface AddonSmokeTestSnapshot {
  id: string;
  name: string;
  manifestUrl: string;
  state: AddonState;
  manifestStatus: 'ok' | 'error';
  streamStatus: 'ok' | 'no_results' | 'error';
  responseTimeMs: number | null;
  resultCount: number | null;
  lastSuccessfulResultAt: string | null;
  lastError: string | null;
  supportsMovie: boolean;
  supportsSeries: boolean;
  supportsAnime: boolean;
  requiresConfig: boolean;
  usableWithoutConfig: boolean;
  priority: number;
  enabled: boolean;
  lastTestedAt: string;
}

interface AddonConfig {
  id: string;
  name: string;
  manifestUrl: string | null;
  priority: number;
  enabled: boolean;
  requiresConfig: boolean;
}

const DEFAULT_ADDONS: AddonConfig[] = [
  { id: 'aiostreams', name: 'AIOStreams', manifestUrl: null, priority: 1, enabled: true, requiresConfig: true },
  { id: 'torrentio-lite', name: 'Torrentio Lite', manifestUrl: 'https://torrentio.strem.fun/lite/manifest.json', priority: 2, enabled: true, requiresConfig: false },
  { id: 'comet', name: 'Comet', manifestUrl: 'https://comet.elfhosted.com/manifest.json', priority: 3, enabled: true, requiresConfig: false },
  { id: 'mediafusion', name: 'MediaFusion', manifestUrl: 'https://mediafusion.elfhosted.com/manifest.json', priority: 4, enabled: false, requiresConfig: false },
];

// ── In-memory store ──
const snapshots = new Map<string, AddonSmokeTestSnapshot>();

function createDefaultSnapshot(addon: AddonConfig): AddonSmokeTestSnapshot {
  const now = new Date().toISOString();
  let state: AddonState;

  if (!addon.enabled) {
    state = 'Disabled';
  } else if (addon.requiresConfig) {
    state = 'NeedsConfig';
  } else {
    state = 'Failed'; // placeholder until first test
  }

  return {
    id: addon.id,
    name: addon.name,
    manifestUrl: addon.manifestUrl || '',
    state,
    manifestStatus: 'error',
    streamStatus: 'error',
    responseTimeMs: null,
    resultCount: null,
    lastSuccessfulResultAt: null,
    lastError: null,
    supportsMovie: false,
    supportsSeries: false,
    supportsAnime: false,
    requiresConfig: addon.requiresConfig,
    usableWithoutConfig: !addon.requiresConfig,
    priority: addon.priority,
    enabled: addon.enabled,
    lastTestedAt: now,
  };
}

// Initialize all addons with default snapshots
for (const addon of DEFAULT_ADDONS) {
  snapshots.set(`default:${addon.id}`, createDefaultSnapshot(addon));
}

// ── CORS + JSON helpers (same pattern as auth_index.ts) ──
const CORS = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type",
};

function json(d: unknown, s = 200): Response {
  return new Response(JSON.stringify(d), {
    status: s,
    headers: { ...CORS, "Content-Type": "application/json" },
  });
}

// ── Test Runner ──

async function testAddon(addon: AddonConfig): Promise<AddonSmokeTestSnapshot> {
  const now = new Date().toISOString();
  const snapshot = createDefaultSnapshot(addon);
  snapshot.lastTestedAt = now;

  // If requires config or no manifest URL, skip to NeedsConfig
  if (!addon.manifestUrl || addon.requiresConfig) {
    snapshot.state = addon.requiresConfig ? 'NeedsConfig' : 'Failed';
    snapshot.lastError = addon.requiresConfig
      ? 'Requires configuration'
      : 'No manifest URL available';
    return snapshot;
  }

  // If disabled
  if (!addon.enabled) {
    snapshot.state = 'Disabled';
    return snapshot;
  }

  // ── Step 1: Fetch manifest ──
  let manifest: Record<string, unknown>;
  try {
    const manifestRes = await fetch(addon.manifestUrl, {
      headers: {
        'User-Agent': 'Unspooled-SmokeTest/1.0',
        'Accept': 'application/json',
      },
    });
    if (!manifestRes.ok) {
      snapshot.manifestStatus = 'error';
      snapshot.state = 'Failed';
      snapshot.lastError = `Manifest HTTP ${manifestRes.status}`;
      return snapshot;
    }
    manifest = (await manifestRes.json()) as Record<string, unknown>;
    if (!manifest.id || !manifest.name) {
      snapshot.manifestStatus = 'error';
      snapshot.state = 'Failed';
      snapshot.lastError = 'Invalid manifest: missing id or name';
      return snapshot;
    }
    snapshot.manifestStatus = 'ok';
    snapshot.name = manifest.name as string;
  } catch (e) {
    snapshot.manifestStatus = 'error';
    snapshot.state = 'Failed';
    snapshot.lastError = `Manifest fetch error: ${(e as Error).message}`;
    return snapshot;
  }

  // ── Step 2: Determine supported types ──
  let supportsMovie = false;
  let supportsSeries = false;
  let supportsAnime = false;

  // Check top-level types
  const manifestTypes = manifest.types as string[] | undefined;
  if (manifestTypes && Array.isArray(manifestTypes)) {
    supportsMovie = manifestTypes.includes('movie');
    supportsSeries = manifestTypes.includes('series');
    supportsAnime =
      manifestTypes.includes('anime') || manifestTypes.includes('anime_series');
  }

  // Check resources for stream resource types
  const resources = manifest.resources as unknown[] | undefined;
  if (resources && Array.isArray(resources)) {
    for (const r of resources) {
      const rid = typeof r === 'string' ? r : (r as Record<string, unknown>).id;
      if (rid === 'stream') {
        const rTypes = (r as Record<string, unknown>).types as string[] | undefined;
        if (rTypes && Array.isArray(rTypes)) {
          supportsMovie = supportsMovie || rTypes.includes('movie');
          supportsSeries = supportsSeries || rTypes.includes('series');
          supportsAnime = supportsAnime || rTypes.includes('anime');
        }
        break;
      }
    }
  }

  snapshot.supportsMovie = supportsMovie;
  snapshot.supportsSeries = supportsSeries;
  snapshot.supportsAnime = supportsAnime;

  // ── Step 3: Derive stream base URL ──
  // Strip /manifest.json from the manifest URL
  const baseUrl = addon.manifestUrl.replace(/\/manifest\.json$/i, '');

  // ── Step 4: Test stream endpoints ──
  let anyStreamOk = false;
  let totalResults = 0;
  let maxResponseTime = 0;
  let lastStreamError: string | null = null;

  const endpoints: { type: string; id: string }[] = [];
  if (supportsMovie) endpoints.push({ type: 'movie', id: 'tt0133093.json' });
  if (supportsSeries) endpoints.push({ type: 'series', id: 'tt0903747:1:1.json' });
  // Fallback: test movie even if no explicit type support declared
  if (endpoints.length === 0) {
    endpoints.push({ type: 'movie', id: 'tt0133093.json' });
  }

  for (const ep of endpoints) {
    const url = `${baseUrl}/stream/${ep.type}/${ep.id}`;
    const start = Date.now();
    try {
      const res = await fetch(url, {
        headers: {
          'User-Agent': 'Unspooled-SmokeTest/1.0',
          'Accept': 'application/json',
        },
      });
      const elapsed = Date.now() - start;
      maxResponseTime = Math.max(maxResponseTime, elapsed);

      if (!res.ok) {
        lastStreamError = `Stream ${ep.type} HTTP ${res.status}`;
        continue;
      }

      const body = (await res.json()) as Record<string, unknown>;
      const streams = body.streams;
      const count = Array.isArray(streams) ? streams.length : 0;
      totalResults += count;

      if (count > 0) {
        anyStreamOk = true;
        snapshot.lastSuccessfulResultAt = new Date().toISOString();
      } else if (!lastStreamError) {
        lastStreamError = `Stream ${ep.type} returned 0 results`;
      }
    } catch (e) {
      const elapsed = Date.now() - start;
      maxResponseTime = Math.max(maxResponseTime, elapsed);
      lastStreamError = `Stream ${ep.type} error: ${(e as Error).message}`;
    }
  }

  snapshot.responseTimeMs = maxResponseTime;
  snapshot.resultCount = totalResults;

  // ── Determine stream status ──
  if (anyStreamOk) {
    snapshot.streamStatus = 'ok';
  } else if (totalResults === 0 && maxResponseTime > 0) {
    snapshot.streamStatus = 'no_results';
  } else {
    snapshot.streamStatus = 'error';
  }

  // ── Determine overall state ──
  // Order of precedence:
  // 1. Slow response (>5s) overrides to Degraded even with results
  // 2. Stream failures or no results
  // 3. Everything ok → Ready
  if (maxResponseTime > 5000) {
    snapshot.state = 'Degraded';
    if (!lastStreamError) lastStreamError = 'Response time exceeds 5s threshold';
  } else if (!anyStreamOk) {
    snapshot.state = totalResults === 0 && maxResponseTime > 0 ? 'NoResults' : 'Degraded';
  } else if (totalResults === 0) {
    snapshot.state = 'NoResults';
  } else {
    snapshot.state = 'Ready';
  }

  snapshot.lastError = lastStreamError;

  return snapshot;
}

/**
 * Run smoke tests for specified addon IDs (or all if omitted).
 * Returns snapshots in priority order.
 */
export async function runSmokeTest(addonIds?: string[]): Promise<AddonSmokeTestSnapshot[]> {
  const toTest = addonIds
    ? DEFAULT_ADDONS.filter(a => addonIds.includes(a.id))
    : DEFAULT_ADDONS;

  const results: AddonSmokeTestSnapshot[] = [];

  for (const addon of toTest) {
    const result = await testAddon(addon);
    snapshots.set(`default:${addon.id}`, result);
    results.push(result);
  }

  // Return in priority order
  results.sort((a, b) => a.priority - b.priority);
  return results;
}

/**
 * Get the latest smoke test status for all configured addons.
 */
export function getSmokeTestStatus(): AddonSmokeTestSnapshot[] {
  return DEFAULT_ADDONS
    .map(a => snapshots.get(`default:${a.id}`))
    .filter((s): s is AddonSmokeTestSnapshot => s !== undefined)
    .sort((a, b) => a.priority - b.priority);
}

/**
 * Get a single addon's snapshot by ID.
 */
export function getAddonSnapshot(id: string): AddonSmokeTestSnapshot | undefined {
  return snapshots.get(`default:${id}`);
}

// ── API Handlers ──

/**
 * POST /api/v1/smoke-test/run
 * Accepts optional body: { addonIds?: string[] }
 */
export async function handleSmokeTestRun(request: Request): Promise<Response> {
  try {
    let addonIds: string[] | undefined;
    if (request.method === 'POST') {
      const body = (await request.json()) as { addonIds?: string[] };
      addonIds = body.addonIds;
    }
    const results = await runSmokeTest(addonIds);
    return json(results, 200);
  } catch (e) {
    return json({ error: (e as Error).message }, 400);
  }
}

/**
 * GET /api/v1/smoke-test/status
 * Returns latest test results for all addons.
 */
export async function handleSmokeTestStatus(): Promise<Response> {
  return json(getSmokeTestStatus(), 200);
}
