/**
 * Pangolin Integration API client for the CCP.
 *
 * Ported from the main CML's pangolin.client.ts. Adapted to:
 * - Accept credentials via constructor (not env singleton)
 * - Be instantiable per-call so the CCP can use its own API token
 *   to manage tunnels for multiple remote instances
 *
 * The CCP never exposes its Pangolin API key to remote instances — it
 * only pushes the resulting Newt credentials via the agent's writeFiles.
 */
import { logger } from '../utils/logger';

// ─── Types ─────────────────────────────────────────────────────────

export interface PangolinSite {
  siteId: string;
  name: string;
  orgId: string;
  niceId: string;
  pubKey?: string;
  subnet?: string;
  megabytesIn?: number;
  megabytesOut?: number;
  lastSeen?: string;
  online?: boolean;
  type?: string;
  address?: string;
}

export interface PangolinExitNode {
  exitNodeId: string;
  name: string;
  location?: string;
  region?: string;
  online: boolean;
  capacity?: number;
  latency?: number;
}

export interface PangolinResource {
  resourceId: string;
  name: string;
  subdomain?: string;
  fullDomain?: string;
  ssl?: boolean;
  blockAccess?: boolean;
  active?: boolean;
  proxyPort?: number;
  protocol?: string;
  domainBindings?: string[];
  http?: boolean;
  targets?: PangolinTarget[];
}

export interface PangolinTarget {
  targetId: string;
  resourceId: string;
  siteId: string;
  ip: string;
  port: number;
  method: string;
  enabled?: boolean;
}

export interface PangolinNewt {
  newtId: string;
  secret: string;
  siteId: string;
}

export interface PangolinSiteDefaults {
  newtId: string;
  newtSecret: string;
  address: string;
}

export interface CreateSitePayload {
  name: string;
  type?: string;
  subnet?: string;
  exitNodeId?: string;
  newtId?: string;
  secret?: string;
  address?: string;
}

export interface CreateHttpResourcePayload {
  name: string;
  domainId: string;
  subdomain?: string;
  http: true;
  protocol: 'tcp';
}

export interface CreateTargetPayload {
  siteId: string | number;
  ip: string;
  port: number;
  method: 'http' | 'https';
  enabled?: boolean;
}

export interface PangolinDomain {
  domainId: string;
  baseDomain: string;
  verified: boolean;
  type?: string;
  failed?: boolean;
  configManaged?: boolean;
}

export interface UpdateResourcePayload {
  name?: string;
  subdomain?: string;
  fullDomain?: string;
  ssl?: boolean;
  sso?: boolean;
  active?: boolean;
  blockAccess?: boolean;
  proxyPort?: number;
  protocol?: string;
  domainBindings?: string[];
}

export interface UpdateCertificatePayload {
  autoRenew?: boolean;
}

export interface PangolinCertificate {
  certId: string;
  domainId: string;
  domain: string;
  status: 'PENDING' | 'ACTIVE' | 'EXPIRED' | 'FAILED';
  issuedAt?: string;
  expiresAt?: string;
  autoRenew?: boolean;
  issuer?: string;
}

export interface PangolinConnectedClient {
  clientId: string;
  resourceId: string;
  ipAddress: string;
  connectedAt: string;
  lastSeen: string;
  bytesIn: number;
  bytesOut: number;
  online: boolean;
}

// ─── Helpers ───────────────────────────────────────────────────────

/** Redact credential fields from Pangolin API request bodies before logging. */
function redactSecrets(body: unknown): unknown {
  if (!body || typeof body !== 'object') return body;
  const obj = body as Record<string, unknown>;
  const redacted = { ...obj };
  if ('secret' in redacted) redacted.secret = '[REDACTED]';
  if ('newtSecret' in redacted) redacted.newtSecret = '[REDACTED]';
  return redacted;
}

// ─── Client ────────────────────────────────────────────────────────

export class CcpPangolinClient {
  constructor(
    private baseUrl: string,
    private apiKey: string,
    private orgId: string
  ) {}

  get configured(): boolean {
    return !!(this.baseUrl && this.apiKey && this.orgId);
  }

  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
    if (!this.configured) {
      throw new Error('Pangolin API not configured. Set PANGOLIN_API_URL, PANGOLIN_API_KEY, PANGOLIN_ORG_ID in CCP .env');
    }

    const url = `${this.baseUrl}${path}`;
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 15000);

    try {
      logger.debug(`[pangolin] ${method} ${path}${body ? ` body=${JSON.stringify(redactSecrets(body))}` : ''}`);

      const res = await fetch(url, {
        method,
        headers: {
          'Authorization': `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });

      if (!res.ok) {
        const text = await res.text().catch(() => '');
        throw new Error(`Pangolin API ${method} ${path} returned ${res.status}: ${text}`);
      }

      const contentType = res.headers.get('content-type') || '';
      if (contentType.includes('application/json')) {
        const json = await res.json();
        return this.unwrapResponse<T>(json);
      }
      return {} as T;
    } finally {
      clearTimeout(timeout);
    }
  }

  private unwrapResponse<T>(json: unknown): T {
    if (json && typeof json === 'object' && !Array.isArray(json)) {
      const obj = json as Record<string, unknown>;
      if ('data' in obj && 'success' in obj) {
        return obj.data as T;
      }
    }
    return json as T;
  }

  // ─── Health ───────────────────────────────────────────────────

  async healthCheck(): Promise<boolean> {
    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 5000);
      try {
        const res = await fetch(`${this.baseUrl}/`, {
          headers: { 'Authorization': `Bearer ${this.apiKey}` },
          signal: controller.signal,
        });
        return res.ok;
      } finally {
        clearTimeout(timeout);
      }
    } catch {
      return false;
    }
  }

  // ─── Site Defaults ────────────────────────────────────────────

  async pickSiteDefaults(): Promise<PangolinSiteDefaults> {
    const res = await this.request<unknown>('GET', `/org/${this.orgId}/pick-site-defaults`);
    const obj = res as Record<string, unknown>;
    const newtId = obj.newtId as string || '';
    const newtSecret = obj.newtSecret as string || obj.secret as string || '';
    const address = obj.clientAddress as string || obj.address as string || '';
    if (!newtId || !newtSecret) {
      throw new Error('Pangolin did not return Newt credentials from pick-site-defaults');
    }
    return { newtId, newtSecret, address };
  }

  // ─── Sites ────────────────────────────────────────────────────

  async listSites(): Promise<PangolinSite[]> {
    const res = await this.request<unknown>('GET', `/org/${this.orgId}/sites`);
    return this.extractArray(res, 'sites', 'listSites');
  }

  async getSite(siteId: string): Promise<PangolinSite> {
    return this.request<PangolinSite>('GET', `/site/${siteId}`);
  }

  async createSite(data: CreateSitePayload): Promise<PangolinSite & { newt?: PangolinNewt }> {
    return this.request<PangolinSite & { newt?: PangolinNewt }>('PUT', `/org/${this.orgId}/site`, data);
  }

  async deleteSite(siteId: string): Promise<void> {
    await this.request<void>('DELETE', `/site/${siteId}`);
  }

  async listExitNodes(): Promise<PangolinExitNode[]> {
    try {
      const res = await this.request<unknown>('GET', `/org/${this.orgId}/exit-nodes`);
      return this.extractArray(res, 'exitNodes', 'listExitNodes');
    } catch {
      return [];
    }
  }

  // ─── Resources ────────────────────────────────────────────────

  async listResources(): Promise<PangolinResource[]> {
    const res = await this.request<unknown>('GET', `/org/${this.orgId}/resources`);
    return this.extractArray(res, 'resources', 'listResources');
  }

  async getResource(resourceId: string): Promise<PangolinResource> {
    return this.request<PangolinResource>('GET', `/resource/${resourceId}`);
  }

  async createResource(data: CreateHttpResourcePayload): Promise<PangolinResource> {
    logger.info(`[pangolin] createResource: ${data.name} (subdomain: ${data.subdomain || '(root)'})`);
    return this.request<PangolinResource>('PUT', `/org/${this.orgId}/resource`, data);
  }

  async updateResource(resourceId: string, data: UpdateResourcePayload): Promise<PangolinResource> {
    return this.request<PangolinResource>('POST', `/resource/${resourceId}`, data);
  }

  async deleteResource(resourceId: string): Promise<void> {
    await this.request<void>('DELETE', `/resource/${resourceId}`);
  }

  // ─── Targets ──────────────────────────────────────────────────

  async createTarget(resourceId: string, data: CreateTargetPayload): Promise<PangolinTarget> {
    logger.info(`[pangolin] createTarget: resource=${resourceId}, ip=${data.ip}:${data.port}`);
    const payload = { ...data, siteId: Number(data.siteId) };
    return this.request<PangolinTarget>('PUT', `/resource/${resourceId}/target`, payload);
  }

  async listTargets(resourceId: string): Promise<PangolinTarget[]> {
    const res = await this.request<unknown>('GET', `/resource/${resourceId}/targets`);
    return this.extractArray(res, 'targets', 'listTargets');
  }

  async deleteTarget(targetId: string): Promise<void> {
    await this.request<void>('DELETE', `/target/${targetId}`);
  }

  // ─── Domains ──────────────────────────────────────────────────

  async listDomains(): Promise<PangolinDomain[]> {
    const res = await this.request<unknown>('GET', `/org/${this.orgId}/domains`);
    return this.extractArray(res, 'domains', 'listDomains');
  }

  // ─── Certificates ─────────────────────────────────────────────

  async getCertificate(domainId: string, domain: string): Promise<PangolinCertificate> {
    return this.request<PangolinCertificate>('GET', `/org/${this.orgId}/certificate/${domainId}/${domain}`);
  }

  async updateCertificate(certId: string, data: UpdateCertificatePayload): Promise<PangolinCertificate> {
    return this.request<PangolinCertificate>('POST', `/certificate/${certId}`, data);
  }

  // ─── Clients ──────────────────────────────────────────────────

  async listClients(resourceId: string): Promise<PangolinConnectedClient[]> {
    const res = await this.request<unknown>('GET', `/resource/${resourceId}/clients`);
    return this.extractArray(res, 'clients', 'listClients');
  }

  // ─── Helpers ──────────────────────────────────────────────────

  private extractArray<T>(res: unknown, key: string, context: string): T[] {
    if (Array.isArray(res)) return res as T[];
    if (res && typeof res === 'object') {
      const obj = res as Record<string, unknown>;
      if (Array.isArray(obj[key])) return obj[key] as T[];
      if (obj.data && typeof obj.data === 'object') {
        const dataObj = obj.data as Record<string, unknown>;
        if (Array.isArray(dataObj[key])) return dataObj[key] as T[];
      }
      if (Array.isArray(obj.data)) return obj.data as T[];
    }
    logger.warn(`[pangolin] ${context}: could not extract array from response`);
    return [];
  }
}
