import { BadRequestException, InternalServerErrorException, Logger } from '@nestjs/common';
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
import { lookup } from 'node:dns';
import { isIP } from 'node:net';
import * as http from 'node:http';
import * as https from 'node:https';
import { AiProvider } from './ai-config.entity';
import { DEFAULT_BASE_URLS } from './dto/ai-config.dto';

export function testFailureResult(message: string, now: string) {
  return {
    success: false,
    latencyMs: null,
    modelCount: null,
    modelAvailable: false,
    testedAt: now,
    message,
  };
}

let _encryptionWarned = false;

export function getEncryptionKey(): Buffer {
  const raw = process.env.AI_CONFIG_ENCRYPTION_KEY;
  if (!raw) {
    if (process.env.NODE_ENV !== 'production') {
      if (!_encryptionWarned) {
        _encryptionWarned = true;
        Logger.warn(
          'AI_CONFIG_ENCRYPTION_KEY 未设置，使用开发回退密钥。生产环境必须配置！',
          'AiConfigService',
        );
      }
      // 32 hex pairs → 32 bytes
      return Buffer.from('ff'.repeat(32), 'hex');
    }
    throw new InternalServerErrorException('AI_CONFIG_ENCRYPTION_KEY 未配置，无法加解密 API Key');
  }

  // Hex: exactly 64 hex chars
  if (/^[0-9a-fA-F]{64}$/.test(raw)) {
    return Buffer.from(raw, 'hex');
  }

  // Base64: decode then re-encode to normalize padding; reject non-canonical forms
  if (/^[A-Za-z0-9+/]+=*$/.test(raw)) {
    const buf = Buffer.from(raw, 'base64');
    if (buf.length !== 32) {
      throw new InternalServerErrorException(
        'AI_CONFIG_ENCRYPTION_KEY 格式无效：base64 解码后须为 32 字节',
      );
    }
    // Re-encode to canonical base64 (no line breaks) and compare
    const canonical = buf.toString('base64');
    if (raw !== canonical) {
      throw new InternalServerErrorException(
        'AI_CONFIG_ENCRYPTION_KEY 格式无效：base64 编码须为标准格式（无多余 padding）',
      );
    }
    return buf;
  }

  throw new InternalServerErrorException(
    'AI_CONFIG_ENCRYPTION_KEY 格式无效：需为 64 位 hex 或 base64 编码的 32 字节密钥',
  );
}

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12;
const AUTH_TAG_LENGTH = 16;

export function encrypt(plaintext: string): { ciphertext: string; iv: string; authTag: string } {
  const key = getEncryptionKey();
  const iv = randomBytes(IV_LENGTH);
  const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return {
    ciphertext: encrypted.toString('base64'),
    iv: iv.toString('base64'),
    authTag: tag.toString('base64'),
  };
}

export function decrypt(ciphertextB64: string, ivB64: string, authTagB64: string): string {
  const key = getEncryptionKey();
  const iv = Buffer.from(ivB64, 'base64');
  const authTag = Buffer.from(authTagB64, 'base64');
  const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
  decipher.setAuthTag(authTag);
  const decrypted = Buffer.concat([
    decipher.update(Buffer.from(ciphertextB64, 'base64')),
    decipher.final(),
  ]);
  return decrypted.toString('utf-8');
}

const PRIVATE_IPV4_RANGES = [
  /^127\./,
  /^10\./,
  /^172\.(1[6-9]|2\d|3[01])\./,
  /^192\.168\./,
  /^169\.254\./,
  /^0\./,
  /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./,
];

export function isPrivateHost(hostname: string): boolean {
  // Strip IPv6 brackets from URL.hostname
  if (hostname.startsWith('[') && hostname.endsWith(']')) {
    hostname = hostname.slice(1, -1);
  }

  if (hostname === 'localhost' || hostname === '0.0.0.0') return true;
  if (hostname.endsWith('.local')) return true;

  if (isIP(hostname) === 6) {
    // IPv6 private/loopback
    if (hostname === '::1' || hostname === '::') return true;
    const lower = hostname.toLowerCase();
    if (lower.startsWith('fc') || lower.startsWith('fd')) return true; // fc00::/7
    if (
      lower.startsWith('fe8') ||
      lower.startsWith('fe9') ||
      lower.startsWith('fea') ||
      lower.startsWith('feb')
    )
      return true; // fe80::/10
    // IPv4-mapped IPv6: ::ffff:0:0/96
    if (lower.startsWith('::ffff:') && isIP(lower.slice(7)) === 4) {
      return PRIVATE_IPV4_RANGES.some((re) => re.test(lower.slice(7)));
    }
    return false;
  }

  if (isIP(hostname) === 4) {
    return PRIVATE_IPV4_RANGES.some((re) => re.test(hostname));
  }

  return false;
}

// Known provider hosts — only these are allowed for fixed providers
const PROVIDER_HOSTS: Partial<Record<AiProvider, readonly string[]>> = {
  [AiProvider.OPENAI]: ['api.openai.com'],
  [AiProvider.DEEPSEEK]: ['api.deepseek.com'],
};

// Required pathname for fixed providers
const PROVIDER_REQUIRED_PATHS: Partial<Record<AiProvider, string>> = {
  [AiProvider.OPENAI]: '/v1',
  [AiProvider.DEEPSEEK]: '/',
};

// Known public provider hosts — always skip DNS private-IP check.
// Their CDN/proxy nodes may resolve to private-range IPs in certain regions.
const DNS_TRUSTED_HOSTS = new Set(['api.openai.com', 'api.deepseek.com']);

export function validateAndNormalizeBaseUrl(url: string | undefined, provider: AiProvider): string {
  const allowPrivate = process.env.AI_ALLOW_PRIVATE_BASE_URL === 'true';

  const raw = url?.trim() || DEFAULT_BASE_URLS[provider];
  if (!raw) {
    throw new BadRequestException('OPENAI_COMPATIBLE 模式必须提供 baseUrl');
  }

  // Reject search/query and hash/fragment
  let parsed: URL;
  try {
    parsed = new URL(raw);
  } catch {
    throw new BadRequestException('请求参数无效');
  }

  if (parsed.search || parsed.hash) {
    throw new BadRequestException('请求参数无效');
  }

  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new BadRequestException('请求参数无效');
  }

  if (process.env.NODE_ENV === 'production' && parsed.protocol === 'http:') {
    throw new BadRequestException('生产环境禁止使用 http://');
  }

  if (parsed.username || parsed.password) {
    throw new BadRequestException('请求参数无效');
  }

  const normalized = parsed.origin + parsed.pathname.replace(/\/+$/, '');

  // Provider-specific host check
  const allowedHosts = PROVIDER_HOSTS[provider];
  if (allowedHosts) {
    if (!allowedHosts.includes(parsed.hostname)) {
      throw new BadRequestException(`${provider} 必须使用固定域名`);
    }
    // Enforce exact path for fixed providers
    const requiredPath = PROVIDER_REQUIRED_PATHS[provider];
    if (
      requiredPath !== undefined &&
      parsed.pathname.replace(/\/+$/, '') !== requiredPath.replace(/\/+$/, '')
    ) {
      throw new BadRequestException(`请求参数无效`);
    }
  } else {
    // OPENAI_COMPATIBLE — SSRF check
    if (!allowPrivate && isPrivateHost(parsed.hostname)) {
      throw new BadRequestException('不允许使用内网地址');
    }
  }

  return normalized;
}

export async function resolveHostnames(
  hostname: string,
): Promise<{ address: string; family: number }[]> {
  return new Promise((resolve, reject) => {
    lookup(hostname, { all: true, family: 0 }, (err, addresses) => {
      if (err) {
        reject(err);
        return;
      }
      if (!addresses || addresses.length === 0) {
        reject(new Error('DNS 解析返回空结果'));
        return;
      }
      resolve(
        addresses.map((a) => ({
          address: a.address,
          family: a.family,
        })),
      );
    });
  });
}

export async function validateDnsNotPrivate(hostname: string): Promise<void> {
  // Trusted public provider hosts — skip DNS check (CDN nodes may resolve to private IPs)
  if (DNS_TRUSTED_HOSTS.has(hostname)) return;

  const allowPrivate = process.env.AI_ALLOW_PRIVATE_BASE_URL === 'true';
  if (allowPrivate) return;

  let addresses: { address: string; family: number }[];
  try {
    addresses = await resolveHostnames(hostname);
  } catch {
    throw new BadRequestException('无法解析域名');
  }

  for (const { address } of addresses) {
    if (isPrivateHost(address)) {
      throw new BadRequestException('域名解析到内网地址');
    }
  }
}

const MAX_RESPONSE_BYTES = 1_048_576; // 1 MiB

/**
 * Perform a pinned HTTP GET request.
 * DNS resolves once; the resolved IP is used for connection, preventing DNS rebinding.
 * Redirects are forbidden. HTTPS certificate validation is enforced.
 */
export function pinnedGet(
  url: string,
  headers: Record<string, string>,
  timeoutMs: number,
): Promise<{ status: number; contentType: string | null; body: string; latencyMs: number }> {
  return new Promise((resolve, reject) => {
    const parsed = new URL(url);
    const isHttps = parsed.protocol === 'https:';
    const port = parsed.port ? parseInt(parsed.port, 10) : isHttps ? 443 : 80;
    const hostname = parsed.hostname;
    const path = parsed.pathname + parsed.search;

    lookup(hostname, { all: true, family: 0 }, (dnsErr, addresses) => {
      if (dnsErr || !addresses || addresses.length === 0) {
        reject(new Error('DNS 解析失败'));
        return;
      }

      const resolved = addresses.find((a) => !isPrivateHost(a.address));
      if (!resolved && process.env.AI_ALLOW_PRIVATE_BASE_URL !== 'true') {
        reject(new Error('解析到内网地址'));
        return;
      }
      const targetIp = resolved ? resolved.address : addresses[0].address;
      const family = resolved ? resolved.family : addresses[0].family;

      const transport = isHttps ? https : http;

      const requestStart = Date.now();

      const req = transport.request(
        {
          hostname: targetIp,
          port,
          path,
          method: 'GET',
          headers: { ...headers, Host: hostname },
          servername: isHttps ? hostname : undefined,
          rejectUnauthorized: isHttps,
          family: family === 6 ? 6 : 4,
          timeout: timeoutMs,
        },
        (res) => {
          const latencyMs = Date.now() - requestStart;
          const status = res.statusCode ?? 500;
          if (status >= 300 && status < 400 && res.headers.location) {
            res.resume();
            res.destroy();
            return reject(new Error('禁止重定向'));
          }

          const contentType = res.headers['content-type'] ?? null;

          const chunks: Buffer[] = [];
          let totalBytes = 0;

          res.on('data', (chunk: Buffer) => {
            totalBytes += chunk.length;
            if (totalBytes > MAX_RESPONSE_BYTES) {
              res.destroy();
              reject(new Error('响应过大'));
              return;
            }
            chunks.push(chunk);
          });
          res.on('end', () => {
            const body = Buffer.concat(chunks).toString('utf-8');
            resolve({ status, contentType, body, latencyMs });
          });

          res.on('error', reject);
        },
      );

      req.on('timeout', () => {
        req.destroy();
        reject(new Error('连接超时'));
      });

      req.on('error', reject);
      req.end();
    });
  });
}
