import { Provide, Inject, ILogger, Logger } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { GeoProxyIpEntity } from '../entity/proxy_ip.js';
import { IProxyProvider, AcquireOpts, ProxyInfo } from '../provider/proxy/interface.js';
import { LocalProxyProvider } from '../provider/proxy/local.js';
import { TianqiProxyProvider } from '../provider/proxy/tianqi.js';

@Provide()
export class GeoProxyIpService extends BaseService {
  @InjectEntityModel(GeoProxyIpEntity)
  proxyIpEntity: Repository<GeoProxyIpEntity>;

  @Logger()
  logger: ILogger;

  private readonly providers = new Map<string, IProxyProvider>([
    ['local', new LocalProxyProvider()],
    ['tianqi', new TianqiProxyProvider()],
  ]);

  /** 按名称获取 Provider，未知名称抛错 */
  getProvider(name: string): IProxyProvider {
    const p = this.providers.get(name);
    if (!p) throw new Error(`Unknown proxy provider: ${name}`);
    return p;
  }

  /** 通过指定 Provider 获取代理 IP */
  async acquire(dto: { provider: string } & AcquireOpts): Promise<ProxyInfo> {
    return this.getProvider(dto.provider).acquire(dto);
  }

  /** 将 ProxyInfo 持久化到数据库（明文存储——后台内部管理工具，无需加密） */
  async persist(
    info: ProxyInfo & { name?: string; provider?: string },
    manager?: any,
  ): Promise<GeoProxyIpEntity> {
    const repo = manager ? manager.getRepository(GeoProxyIpEntity) : this.proxyIpEntity;
    const entity = repo.create({
      name: info.name || `${info.mode}-${info.region || 'unknown'}`,
      provider: info.provider || (info.mode === 'local' ? 'local' : 'tianqi'),
      mode: info.mode,
      host: info.host,
      port: info.port,
      protocol: info.protocol,
      username: info.username,
      password: info.password,
      region: info.region,
      isp: info.isp,
      externalId: info.externalId,
      status: 'active',
      expiresAt: info.expiresAt,
    });
    return repo.save(entity);
  }

  /** 数据库实体 → ProxyInfo（明文直读） */
  toProxyInfo(e: GeoProxyIpEntity): ProxyInfo {
    return {
      externalId: e.externalId,
      mode: e.mode as any,
      protocol: e.protocol as any,
      host: e.host,
      port: e.port,
      username: e.username || undefined,
      password: e.password || undefined,
      region: e.region,
      isp: e.isp,
      expiresAt: e.expiresAt,
    };
  }

  /**
   * 测试单个 IP 连通性（入站+出站）：
   * - 通过代理请求 https://httpbin.org/ip → 验证代理能转发（入站通）
   * - 解析返回的 origin → 验证出口 IP（出站正常 + IP 是否符合预期）
   * 返回详细诊断信息给前端展示
   */
  async testProxy(id: number): Promise<{ ok: boolean; latencyMs: number; exitIp?: string; expectedIp?: string; ipMatch?: boolean; error?: string }> {
    const ip = await this.proxyIpEntity.findOneBy({ id });
    if (!ip) return { ok: false, latencyMs: 0, error: `IP ${id} not found` };
    const info = this.toProxyInfo(ip);
    const expectedIp = ip.host;
    const startedAt = Date.now();
    try {
      const exitIp = await this.fetchExitIpThroughProxy(info);
      const latencyMs = Date.now() - startedAt;
      const ipMatch = !!exitIp && (exitIp === expectedIp || exitIp === ip.exitIp);
      // 把测试结果写回 entity（exitIp + 状态 + 延迟）
      await this.proxyIpEntity.update(ip.id, {
        exitIp: exitIp || ip.exitIp,
        latencyMs,
        lastCheckAt: new Date(),
        status: 'active',
      });
      return { ok: true, latencyMs, exitIp, expectedIp, ipMatch };
    } catch (e: any) {
      const latencyMs = Date.now() - startedAt;
      await this.proxyIpEntity.update(ip.id, {
        latencyMs,
        lastCheckAt: new Date(),
        status: 'error',
      });
      return { ok: false, latencyMs, error: e.message ?? String(e) };
    }
  }

  /** 通过代理请求 httpbin.org/ip 拿出口 IP（支持 SOCKS5 + HTTP 认证） */
  private async fetchExitIpThroughProxy(p: ProxyInfo): Promise<string> {
    if (!p.host || !p.port) throw new Error('proxy host/port missing');
    const { SocksClient } = await import('socks');
    const tls = await import('node:tls');
    const target = { host: 'httpbin.org', port: 443 };

    let socket: any;
    if (p.protocol === 'socks5') {
      const conn = await SocksClient.createConnection({
        proxy: { host: p.host, port: p.port, type: 5, userId: p.username, password: p.password },
        command: 'connect',
        destination: target,
        timeout: 8000,
      });
      socket = conn.socket;
    } else {
      // HTTP CONNECT
      const net = await import('node:net');
      socket = await new Promise<any>((resolve, reject) => {
        const s = net.connect(p.port!, p.host!);
        s.once('error', reject);
        s.once('connect', () => {
          const auth = p.username ? 'Proxy-Authorization: Basic ' + Buffer.from(`${p.username}:${p.password}`).toString('base64') + '\r\n' : '';
          s.write(`CONNECT ${target.host}:${target.port} HTTP/1.1\r\nHost: ${target.host}:${target.port}\r\n${auth}\r\n`);
          let buf = '';
          const onData = (d: Buffer) => {
            buf += d.toString();
            if (buf.includes('\r\n\r\n')) {
              s.removeListener('data', onData);
              if (/^HTTP\/1\.[01] 200/.test(buf)) resolve(s);
              else reject(new Error('CONNECT failed: ' + buf.split('\r\n')[0]));
            }
          };
          s.on('data', onData);
        });
      });
    }

    // TLS upgrade + HTTP GET
    return new Promise<string>((resolve, reject) => {
      const tlsSocket = tls.connect({ socket, servername: target.host, ALPNProtocols: ['http/1.1'] }, () => {
        tlsSocket.write('GET /ip HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n');
      });
      let body = '';
      const timer = setTimeout(() => { tlsSocket.destroy(); reject(new Error('proxy test timeout')); }, 10000);
      tlsSocket.on('data', (d: Buffer) => { body += d.toString(); });
      tlsSocket.on('end', () => {
        clearTimeout(timer);
        try {
          const i = body.indexOf('\r\n\r\n');
          const json = JSON.parse(body.slice(i + 4));
          resolve(json.origin?.split(',')[0]?.trim() || '');
        } catch (e: any) {
          reject(new Error('parse exit ip failed: ' + e.message));
        }
      });
      tlsSocket.on('error', (e: any) => { clearTimeout(timer); reject(e); });
    });
  }

  /** 对所有 active 状态的 IP 执行健康检查并更新状态 */
  async healthCheckAll(): Promise<void> {
    const ips = await this.proxyIpEntity.find({ where: { status: 'active' } });
    for (const ip of ips) {
      try {
        const result = await this.getProvider(ip.provider).healthCheck(this.toProxyInfo(ip));
        await this.proxyIpEntity.update(ip.id, {
          latencyMs: result.latencyMs,
          lastCheckAt: new Date(),
          status: result.ok ? 'active' : 'error',
        });
        if (!result.ok) this.logger?.warn?.(`[GEO] IP ${ip.id} health check failed`);
      } catch (e: any) {
        this.logger?.error?.(`[GEO] IP ${ip.id} health check error: ${e.message}`);
      }
    }
  }
}
