import * as https from "https";
import * as vscode from "vscode";

/**
 * Periodically syncs the local MCP server catalog from the public
 * marketplace manifest. Checks checksums to detect changes and
 * handles force-update flags for critical security patches.
 */

interface ManifestData {
  version: string;
  updated_at: string;
  checksum: string;
  catalog_url: string;
}

interface CatalogSecurity {
  min_client_version?: string;
  force_update_versions?: string[];
  deprecated_servers?: string[];
  advisories?: string[];
}

interface CatalogData {
  version: string;
  servers: unknown[];
  security?: CatalogSecurity;
}

const MANIFEST_URL =
  "https://grid.mcpstudio.qabots.diy/api/v1/manifest.json";
const CATALOG_URL =
  "https://grid.mcpstudio.qabots.diy/api/v1/catalog.json";
const SYNC_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
const CURRENT_VERSION = "1.0.0-beta-rc";

export class CatalogSyncService {
  private _timer: ReturnType<typeof setInterval> | undefined;
  private _lastChecksum: string | undefined;
  private _disposables: vscode.Disposable[] = [];

  constructor(private readonly _context: vscode.ExtensionContext) {}

  /** Start the hourly sync loop. */
  start(): void {
    // First sync shortly after startup (10s delay)
    const initialDelay = setTimeout(() => this._sync(), 10_000);
    this._disposables.push({ dispose: () => clearTimeout(initialDelay) });

    // Then every hour
    this._timer = setInterval(() => this._sync(), SYNC_INTERVAL_MS);
    this._disposables.push({ dispose: () => clearInterval(this._timer!) });
  }

  /** Stop the sync loop. */
  dispose(): void {
    for (const d of this._disposables) {
      d.dispose();
    }
    this._disposables = [];
  }

  /** Perform a single sync check. */
  private async _sync(): Promise<void> {
    try {
      const manifest = await this._fetchJson<ManifestData>(MANIFEST_URL);

      // Skip if checksum hasn't changed
      if (manifest.checksum && manifest.checksum === this._lastChecksum) {
        return;
      }

      // Fetch the full catalog
      const catalog = await this._fetchJson<CatalogData>(
        manifest.catalog_url || CATALOG_URL
      );

      // Verify checksum matches (basic integrity)
      this._lastChecksum = manifest.checksum;

      // Check for force-update requirements
      await this._checkForceUpdates(catalog);

      // Store the catalog version for the extension to use
      await this._context.globalState.update(
        "mcpGrid.lastCatalogVersion",
        catalog.version
      );
      await this._context.globalState.update(
        "mcpGrid.lastCatalogSync",
        new Date().toISOString()
      );

      console.log(
        `[MCP Grid] Catalog synced: v${catalog.version}, ${catalog.servers?.length ?? 0} servers`
      );
    } catch (err) {
      // Non-fatal -- silently retry next cycle
      console.debug("[MCP Grid] Catalog sync failed (non-fatal):", err);
    }
  }

  /** Check for critical updates that require force-update. */
  private async _checkForceUpdates(catalog: CatalogData): Promise<void> {
    const security = catalog.security;
    if (!security) {
      return;
    }

    // Check if current version is in force_update list
    const forceVersions = security.force_update_versions ?? [];
    if (forceVersions.includes(CURRENT_VERSION)) {
      vscode.window.showWarningMessage(
        `MCP Grid: A critical security update is available. Please update to the latest version.`,
        "Update Now"
      ).then((choice) => {
        if (choice === "Update Now") {
          vscode.env.openExternal(
            vscode.Uri.parse(
              "https://github.com/IB-QA/MCPServerGridStudio/releases/latest"
            )
          );
        }
      });
    }

    // Check min_client_version
    if (
      security.min_client_version &&
      this._versionLessThan(CURRENT_VERSION, security.min_client_version)
    ) {
      vscode.window.showErrorMessage(
        `MCP Grid: Your version (${CURRENT_VERSION}) is below the minimum required version (${security.min_client_version}). Please update immediately.`,
        "Update Now"
      ).then((choice) => {
        if (choice === "Update Now") {
          vscode.env.openExternal(
            vscode.Uri.parse(
              "https://github.com/IB-QA/MCPServerGridStudio/releases/latest"
            )
          );
        }
      });
    }

    // Show advisories
    const advisories = security.advisories ?? [];
    for (const advisory of advisories) {
      vscode.window.showWarningMessage(`MCP Grid Advisory: ${advisory}`);
    }
  }

  /** Rough semver comparison: returns true if a < b. */
  private _versionLessThan(a: string, b: string): boolean {
    const normalize = (v: string) =>
      v.replace(/-.*$/, "").split(".").map(Number);
    const pa = normalize(a);
    const pb = normalize(b);
    for (let i = 0; i < 3; i++) {
      if ((pa[i] ?? 0) < (pb[i] ?? 0)) return true;
      if ((pa[i] ?? 0) > (pb[i] ?? 0)) return false;
    }
    return false;
  }

  /** Fetch JSON from a URL. */
  private _fetchJson<T>(url: string): Promise<T> {
    return new Promise((resolve, reject) => {
      https
        .get(url, { timeout: 15_000 }, (res) => {
          if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
            reject(new Error(`HTTP ${res.statusCode}`));
            return;
          }
          let data = "";
          res.on("data", (chunk) => (data += chunk));
          res.on("end", () => {
            try {
              resolve(JSON.parse(data) as T);
            } catch {
              reject(new Error("Invalid JSON"));
            }
          });
        })
        .on("error", reject)
        .on("timeout", () => reject(new Error("Timeout")));
    });
  }
}
