import { assert, assertEquals } from '@std/assert';

import { ExternalGatewayManager } from '../ts/classes/external-gateway.ts';
import type { IDomain, IService, ISslCertificate } from '../ts/types.ts';

class FakeDatabase {
  public settings = new Map<string, string>();
  public secretSettings = new Map<string, string>();
  public domains: IDomain[] = [];
  public services: IService[] = [];
  public certificates = new Map<string, ISslCertificate>();
  private nextDomainId = 1;

  getSetting(key: string): string | null {
    return this.settings.get(key) ?? null;
  }

  setSetting(key: string, value: string): void {
    this.settings.set(key, value);
  }

  async getSecretSetting(key: string): Promise<string | null> {
    return this.secretSettings.get(key) ?? null;
  }

  getDomainByName(domain: string): IDomain | null {
    return this.domains.find((entry) => entry.domain === domain) ?? null;
  }

  createDomain(domain: Omit<IDomain, 'id'>): IDomain {
    const createdDomain = { ...domain, id: this.nextDomainId++ };
    this.domains.push(createdDomain);
    return createdDomain;
  }

  updateDomain(id: number, updates: Partial<IDomain>): void {
    const index = this.domains.findIndex((entry) => entry.id === id);
    if (index === -1) return;
    this.domains[index] = { ...this.domains[index], ...updates };
  }

  getDomainsByProvider(provider: NonNullable<IDomain['dnsProvider']>): IDomain[] {
    return this.domains.filter((entry) => entry.dnsProvider === provider);
  }

  getAllServices(): IService[] {
    return this.services;
  }

  getSSLCertificate(domain: string): ISslCertificate | null {
    return this.certificates.get(domain) ?? null;
  }

  updateSSLCertificate(domain: string, updates: Partial<ISslCertificate>): void {
    const existing = this.certificates.get(domain);
    if (!existing) return;
    this.certificates.set(domain, { ...existing, ...updates });
  }

  async createSSLCertificate(cert: Omit<ISslCertificate, 'id'>): Promise<ISslCertificate> {
    const storedCert = { ...cert, id: this.certificates.size + 1 };
    this.certificates.set(cert.domain, storedCert);
    return storedCert;
  }
}

const makeOneboxRef = () => {
  const database = new FakeDatabase();
  database.settings.set('dcrouterGatewayUrl', 'https://edge.example.com');
  database.secretSettings.set('dcrouterGatewayApiToken', 'dcr-token');

  let reloadCount = 0;
  return {
    database,
    reverseProxy: {
      reloadCertificates: async () => {
        reloadCount++;
      },
      get reloadCount() {
        return reloadCount;
      },
    },
  };
};

Deno.test('ExternalGatewayManager syncs dcrouter domains into Onebox domains', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.domains.push({
    id: 99,
    domain: 'old.example.com',
    dnsProvider: 'dcrouter',
    isObsolete: false,
    defaultWildcard: true,
    createdAt: 1,
    updatedAt: 1,
  });

  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
    }
    assertEquals(method, 'getGatewayClientDomains');
    assertEquals(requestData.gatewayClientId, 'onebox-token');
    return {
      domains: [
        {
          name: 'example.com',
          capabilities: {
            canCreateSubdomains: true,
            canManageDnsRecords: true,
            canIssueCertificates: true,
            canHostEmail: true,
          },
        },
      ],
    };
  };

  const domains = await manager.syncDomains();

  assertEquals(domains.length, 2);
  assertEquals(oneboxRef.database.getDomainByName('example.com')?.dnsProvider, 'dcrouter');
  assertEquals(oneboxRef.database.getDomainByName('example.com')?.defaultWildcard, true);
  assertEquals(oneboxRef.database.getDomainByName('old.example.com')?.isObsolete, true);
});

Deno.test('ExternalGatewayManager syncs service routes to dcrouter gatewayClient API', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.settings.set('serverIP', '203.0.113.10');
  oneboxRef.database.settings.set('httpPort', '8080');

  const service: IService = {
    id: 1,
    name: 'hello',
    image: 'nginx:latest',
    envVars: {},
    port: 3000,
    domain: 'hello.example.com',
    status: 'running',
    createdAt: 1,
    updatedAt: 1,
  };

  const requests: Array<{ method: string; requestData: Record<string, unknown> }> = [];
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
    }
    requests.push({ method, requestData });
    if (method === 'exportCertificate') {
      return { success: false };
    }
    return { success: true, action: 'created', routeId: 'route-1' };
  };

  await manager.syncServiceRoute(service);

  const syncRequest = requests.find((request) => request.method === 'syncGatewayClientRoute')!;
  const route = syncRequest.requestData.route as any;
  const ownership = syncRequest.requestData.ownership as any;

  assertEquals(ownership, {
    gatewayClientType: 'onebox',
    gatewayClientId: 'onebox-token',
    appId: 'hello',
    hostname: 'hello.example.com',
  });
  assertEquals(route.match, { ports: [443], domains: ['hello.example.com'] });
  assertEquals(route.action.targets, [{ host: '203.0.113.10', port: 8080 }]);
  assertEquals(route.action.tls, { mode: 'terminate', certificate: 'auto' });
  assertEquals(syncRequest.requestData.enabled, true);
});

Deno.test('ExternalGatewayManager syncs Admin UI route to dcrouter gatewayClient API', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.settings.set('adminUiDomain', 'Onebox.Example.com');
  oneboxRef.database.settings.set('serverIP', '203.0.113.10');
  oneboxRef.database.settings.set('httpPort', '8080');

  const requests: Array<{ method: string; requestData: Record<string, unknown> }> = [];
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (
    method: string,
    requestData: Record<string, unknown>,
  ) => {
    if (method === 'getGatewayClientContext') {
      return {
        context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } },
      };
    }
    requests.push({ method, requestData });
    if (method === 'exportCertificate') {
      return { success: false };
    }
    return { success: true, action: 'created', routeId: 'admin-route' };
  };

  await manager.syncAdminUiRoute();

  const syncRequest = requests.find((request) => request.method === 'syncGatewayClientRoute')!;
  const route = syncRequest.requestData.route as any;
  const ownership = syncRequest.requestData.ownership as any;

  assertEquals(ownership, {
    gatewayClientType: 'onebox',
    gatewayClientId: 'onebox-token',
    appId: 'onebox-admin-ui',
    hostname: 'onebox.example.com',
  });
  assertEquals(route.match, { ports: [443], domains: ['onebox.example.com'] });
  assertEquals(route.action.targets, [{ host: '203.0.113.10', port: 8080 }]);
  assertEquals(syncRequest.requestData.enabled, true);
});

Deno.test('ExternalGatewayManager uses managed dcrouter local target in managed mode', async () => {
  const oneboxRef = makeOneboxRef();
  (oneboxRef as any).managedDcRouter = {
    getMode: () => 'managed',
    getGatewayUrl: () => 'http://127.0.0.1:3300',
    getAdminToken: async () => 'dcr-managed-token',
    ensureGatewayClientId: () => 'onebox-managed',
    getRouteTarget: () => ({ host: 'onebox-smartproxy', port: 80 }),
  };

  const service: IService = {
    id: 1,
    name: 'hello',
    image: 'nginx:latest',
    envVars: {},
    port: 3000,
    domain: 'hello.example.com',
    status: 'running',
    createdAt: 1,
    updatedAt: 1,
  };

  let syncRequest: Record<string, unknown> | null = null;
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>, config: any) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'admin' } };
    }
    if (method === 'exportCertificate') {
      return { success: false };
    }
    assertEquals(config.url, 'http://127.0.0.1:3300');
    assertEquals(config.apiToken, 'dcr-managed-token');
    syncRequest = requestData;
    return { success: true, action: 'created', routeId: 'route-1' };
  };

  await manager.syncServiceRoute(service);

  assert(syncRequest);
  const route = (syncRequest as Record<string, unknown>).route as any;
  const ownership = (syncRequest as Record<string, unknown>).ownership as any;
  assertEquals(ownership.gatewayClientId, 'onebox-managed');
  assertEquals(route.action.targets, [{ host: 'onebox-smartproxy', port: 80 }]);
});

Deno.test('ExternalGatewayManager deletes service routes through dcrouter gatewayClient API', async () => {
  const oneboxRef = makeOneboxRef();
  const manager = new ExternalGatewayManager(oneboxRef as any);
  let deleteRequest: Record<string, unknown> | null = null;

  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
    }
    assertEquals(method, 'syncGatewayClientRoute');
    deleteRequest = requestData;
    return { success: true, action: 'deleted', routeId: 'route-1' };
  };

  await manager.deleteServiceRoute({
    id: 1,
    name: 'hello',
    domain: 'hello.example.com',
  });

  assert(deleteRequest);
  const capturedDeleteRequest = deleteRequest as Record<string, unknown>;
  assertEquals(capturedDeleteRequest.delete, true);
  assertEquals((capturedDeleteRequest.ownership as any).gatewayClientId, 'onebox-token');
  assertEquals((capturedDeleteRequest.ownership as any).hostname, 'hello.example.com');
});

Deno.test('ExternalGatewayManager removes stale gateway routes during reconciliation', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.settings.set('serverIP', '203.0.113.10');
  oneboxRef.database.services.push({
    id: 1,
    name: 'active',
    image: 'nginx:latest',
    envVars: {},
    port: 3000,
    domain: 'active.example.com',
    status: 'running',
    createdAt: 1,
    updatedAt: 1,
  });

  const deletes: Record<string, unknown>[] = [];
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
    }
    if (method === 'syncGatewayClientRoute') {
      if (requestData.delete) {
        deletes.push(requestData);
        return { success: true, action: 'deleted' };
      }
      return { success: true, action: 'updated', routeId: 'active-route' };
    }
    if (method === 'exportCertificate') {
      return { success: false };
    }
    if (method === 'getGatewayClientDnsRecords') {
      return {
        records: [
          {
            id: 'active-record',
            domainId: 'domain-1',
            name: 'active',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'active',
            hostname: 'active.example.com',
            routeId: 'active-route',
          },
          {
            id: 'stale-record',
            domainId: 'domain-1',
            name: 'stale',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'stale',
            hostname: 'stale.example.com',
            routeId: 'stale-route',
          },
        ],
      };
    }
    throw new Error(`Unexpected method: ${method}`);
  };

  await manager.syncServiceRoutes();

  assertEquals(deletes.length, 1);
  assertEquals((deletes[0].ownership as any).hostname, 'stale.example.com');
});

Deno.test('ExternalGatewayManager preserves configured Admin UI route during reconciliation', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.settings.set('adminUiDomain', 'onebox.example.com');
  oneboxRef.database.settings.set('serverIP', '203.0.113.10');
  oneboxRef.database.services.push({
    id: 1,
    name: 'active',
    image: 'nginx:latest',
    envVars: {},
    port: 3000,
    domain: 'active.example.com',
    status: 'running',
    createdAt: 1,
    updatedAt: 1,
  });

  const deletes: Record<string, unknown>[] = [];
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
    }
    if (method === 'syncGatewayClientRoute') {
      if (requestData.delete) {
        deletes.push(requestData);
        return { success: true, action: 'deleted' };
      }
      return { success: true, action: 'updated' };
    }
    if (method === 'exportCertificate') {
      return { success: false };
    }
    if (method === 'getGatewayClientDnsRecords') {
      return {
        records: [
          {
            id: 'admin-record',
            domainId: 'domain-1',
            name: 'onebox',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'onebox-admin-ui',
            hostname: 'onebox.example.com',
            routeId: 'admin-route',
          },
          {
            id: 'stale-record',
            domainId: 'domain-1',
            name: 'stale',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'stale',
            hostname: 'stale.example.com',
            routeId: 'stale-route',
          },
        ],
      };
    }
    throw new Error(`Unexpected method: ${method}`);
  };

  await manager.syncServiceRoutes();

  assertEquals(deletes.length, 1);
  assertEquals((deletes[0].ownership as any).hostname, 'stale.example.com');
});

Deno.test('ExternalGatewayManager preserves legacy Admin UI route when setting is absent', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.settings.set('serverIP', '203.0.113.10');

  const deletes: Record<string, unknown>[] = [];
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (
    method: string,
    requestData: Record<string, unknown>,
  ) => {
    if (method === 'getGatewayClientContext') {
      return {
        context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } },
      };
    }
    if (method === 'syncGatewayClientRoute') {
      if (requestData.delete) {
        deletes.push(requestData);
        return { success: true, action: 'deleted' };
      }
      return { success: true, action: 'updated' };
    }
    if (method === 'getGatewayClientDnsRecords') {
      return {
        records: [
          {
            id: 'legacy-admin-record',
            domainId: 'domain-1',
            name: 'onebox',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'onebox',
            hostname: 'onebox.example.com',
            routeId: 'legacy-admin-route',
          },
          {
            id: 'stale-record',
            domainId: 'domain-1',
            name: 'stale',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'stale',
            hostname: 'stale.example.com',
            routeId: 'stale-route',
          },
        ],
      };
    }
    throw new Error(`Unexpected method: ${method}`);
  };

  await manager.syncServiceRoutes();

  assertEquals(deletes.length, 1);
  assertEquals((deletes[0].ownership as any).hostname, 'stale.example.com');
});

Deno.test('ExternalGatewayManager deletes old Admin UI route after domain change', async () => {
  const oneboxRef = makeOneboxRef();
  oneboxRef.database.settings.set('adminUiDomain', 'new.example.com');
  oneboxRef.database.settings.set('serverIP', '203.0.113.10');

  const deletes: Record<string, unknown>[] = [];
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (
    method: string,
    requestData: Record<string, unknown>,
  ) => {
    if (method === 'getGatewayClientContext') {
      return {
        context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } },
      };
    }
    if (method === 'syncGatewayClientRoute') {
      if (requestData.delete) {
        deletes.push(requestData);
        return { success: true, action: 'deleted' };
      }
      return { success: true, action: 'updated' };
    }
    if (method === 'exportCertificate') {
      return { success: false };
    }
    if (method === 'getGatewayClientDnsRecords') {
      return {
        records: [
          {
            id: 'old-admin-record',
            domainId: 'domain-1',
            name: 'onebox',
            type: 'A',
            value: '203.0.113.10',
            ttl: 300,
            source: 'route',
            status: 'active',
            gatewayClientType: 'onebox',
            gatewayClientId: 'onebox-token',
            appId: 'onebox-admin-ui',
            hostname: 'old.example.com',
            routeId: 'old-admin-route',
          },
        ],
      };
    }
    throw new Error(`Unexpected method: ${method}`);
  };

  await manager.syncServiceRoutes();

  assertEquals(deletes.length, 1);
  assertEquals((deletes[0].ownership as any).hostname, 'old.example.com');
});

Deno.test('ExternalGatewayManager imports exported dcrouter certificates into Onebox', async () => {
  const oneboxRef = makeOneboxRef();
  const manager = new ExternalGatewayManager(oneboxRef as any);
  (manager as any).fireDcRouterRequest = async (method: string, requestData: Record<string, unknown>) => {
    if (method === 'getGatewayClientContext') {
      return { context: { role: 'gatewayClient', gatewayClient: { type: 'onebox', id: 'onebox-token' } } };
    }
    assertEquals(method, 'exportCertificate');
    assertEquals(requestData.domain, 'hello.example.com');
    return {
      success: true,
      cert: {
        id: 'cert-1',
        domainName: 'hello.example.com',
        created: 1,
        validUntil: 2,
        privateKey: '-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----',
        publicKey: '-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----',
        csr: '',
      },
    };
  };

  const imported = await manager.importCertificateForDomain('hello.example.com');

  assert(imported);
  assertEquals(oneboxRef.database.getSSLCertificate('hello.example.com')?.issuer, 'dcrouter');
  assertEquals(oneboxRef.reverseProxy.reloadCount, 1);
});
