import { query, queryOne } from './db.js';
import { HttpError } from './http.js';
import { config as appConfig } from './config.js';

export type TenantSecretScope = 'payment' | 'sms' | 'oauth' | 'storage' | 'crm' | 'ai' | 'system';

export interface TenantSecretConfig {
  secretScope: TenantSecretScope;
  secretKey: string;
  secretValue: string | null;
  secretJson: Record<string, unknown>;
}

export interface TenantProviderConfig {
  tenantId: string;
  provider: string;
  status: string;
  configPublic: Record<string, unknown>;
  secret: TenantSecretConfig | null;
}

export type TenantAuthProviderConfig = TenantProviderConfig;
export type TenantPaymentProviderConfig = TenantProviderConfig;

interface ProviderRow {
  provider: string;
  status: string;
  configPublic: Record<string, unknown> | null;
}

interface TenantSecretRow {
  secretScope: TenantSecretScope;
  secretKey: string;
  secretValue: string | null;
  secretJson: Record<string, unknown> | null;
}

function objectValue(value: unknown): Record<string, unknown> {
  return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
}

function parseSecretRef(value: unknown): { scope: TenantSecretScope; key: string } | null {
  if (typeof value !== 'string' || !value.trim()) return null;
  const parts = value.trim().split(':');
  if (parts.length !== 3 || parts[0] !== 'app_private.tenant_secrets') return null;
  const scope = parts[1] as TenantSecretScope;
  const key = parts[2];
  if (!['payment', 'sms', 'oauth', 'storage', 'crm', 'ai', 'system'].includes(scope) || !key) return null;
  return { scope, key };
}

function defaultSecretScope(provider: string): TenantSecretScope {
  const normalized = provider.toLowerCase().replace(/[_\s]/g, '-');
  if (normalized.includes('sms') || normalized === 'aliyun' || normalized === 'tencent') return 'sms';
  if (normalized.includes('pay') || normalized.includes('alipay')) return 'payment';
  return 'oauth';
}

async function loadTenantSecret(
  tenantId: string,
  configPublic: Record<string, unknown>,
  provider: string,
): Promise<TenantSecretConfig | null> {
  const secretRef = parseSecretRef(configPublic.secretRef);
  const scope = secretRef?.scope || defaultSecretScope(provider);
  const key = secretRef?.key || provider;

  const row = await queryOne<TenantSecretRow>(
    `
    select secret_scope as "secretScope", secret_key as "secretKey",
           secret_value as "secretValue", secret_json as "secretJson"
    from app_private.tenant_secrets
    where tenant_id = $1
      and secret_scope = $2
      and secret_key = $3
    limit 1
    `,
    [tenantId, scope, key],
  );

  if (!row) return null;
  return {
    secretScope: row.secretScope,
    secretKey: row.secretKey,
    secretValue: row.secretValue,
    secretJson: objectValue(row.secretJson),
  };
}

async function loadTenantProviderConfig(input: {
  table: 'tenant_auth_providers' | 'tenant_payment_accounts';
  tenantId: string;
  providers: string[];
  allowedStatuses: string[];
}): Promise<TenantProviderConfig | null> {
  const uniqueProviders = [...new Set(input.providers.map(item => item.trim()).filter(Boolean))];
  if (uniqueProviders.length === 0) return null;

  const rows = await query<ProviderRow>(
    `
    select provider, status, config_public as "configPublic"
    from public.${input.table}
    where tenant_id = $1
      and provider = any($2::text[])
      and status = any($3::text[])
    `,
    [input.tenantId, uniqueProviders, input.allowedStatuses],
  );

  const row = uniqueProviders.map(provider => rows.find(item => item.provider === provider)).find(Boolean);
  if (!row) return null;

  const configPublic = objectValue(row.configPublic);
  const secret = await loadTenantSecret(input.tenantId, configPublic, row.provider);
  return {
    tenantId: input.tenantId,
    provider: row.provider,
    status: row.status,
    configPublic,
    secret,
  };
}

export function loadTenantAuthProviderConfig(tenantId: string, providers: string[]) {
  return loadTenantProviderConfig({
    table: 'tenant_auth_providers',
    tenantId,
    providers,
    allowedStatuses: ['active', 'testing'],
  });
}

export function loadTenantPaymentProviderConfig(tenantId: string, providers: string[]) {
  return loadTenantProviderConfig({
    table: 'tenant_payment_accounts',
    tenantId,
    providers,
    allowedStatuses: ['active'],
  });
}

export function requirePublicString(config: TenantProviderConfig, keys: string[], code: string) {
  for (const key of keys) {
    const value = config.configPublic[key];
    if (typeof value === 'string' && value.trim()) return value.trim();
  }
  throw new HttpError(503, `${config.provider} public config is missing ${keys[0]}`, code);
}

export function optionalPublicString(config: TenantProviderConfig, keys: string[]) {
  for (const key of keys) {
    const value = config.configPublic[key];
    if (typeof value === 'string' && value.trim()) return value.trim();
  }
  return '';
}

function isLocalDevHost(hostname: string) {
  return ['127.0.0.1', 'localhost', '::1'].includes(hostname);
}

function isAllowedHost(hostname: string, allowedHosts: string[]) {
  return allowedHosts.some(host => hostname === host || hostname.endsWith(`.${host}`));
}

export function providerEndpoint(
  providerConfig: TenantProviderConfig,
  fallback: string,
  allowedHosts: string[],
  code = 'PROVIDER_ENDPOINT_NOT_ALLOWED',
) {
  return providerEndpointForKeys(providerConfig, ['endpoint'], fallback, allowedHosts, code);
}

export function providerEndpointForKeys(
  providerConfig: TenantProviderConfig,
  keys: string[],
  fallback: string,
  allowedHosts: string[],
  code = 'PROVIDER_ENDPOINT_NOT_ALLOWED',
) {
  const raw = optionalPublicString(providerConfig, keys) || fallback;
  let endpoint: URL;
  try {
    endpoint = new URL(raw);
  } catch {
    throw new HttpError(503, `${providerConfig.provider} endpoint is invalid`, code);
  }

  const localDev = !appConfig.isProduction && isLocalDevHost(endpoint.hostname);
  if (!localDev && endpoint.protocol !== 'https:') {
    throw new HttpError(503, `${providerConfig.provider} endpoint must use HTTPS`, code);
  }
  if (!localDev && !isAllowedHost(endpoint.hostname, allowedHosts)) {
    throw new HttpError(503, `${providerConfig.provider} endpoint host is not allowed`, code);
  }
  endpoint.username = '';
  endpoint.password = '';
  return endpoint.toString();
}

export function optionalPublicObject(config: TenantProviderConfig, key: string) {
  return objectValue(config.configPublic[key]);
}

export function optionalPublicArray(config: TenantProviderConfig, key: string) {
  const value = config.configPublic[key];
  return Array.isArray(value) ? value : [];
}

export function optionalPublicBoolean(config: TenantProviderConfig, key: string, fallback = false) {
  const value = config.configPublic[key];
  return typeof value === 'boolean' ? value : fallback;
}

export function secretString(config: TenantProviderConfig, keys: string[]) {
  const secret = config.secret;
  if (!secret) return '';
  if (typeof secret.secretValue === 'string' && secret.secretValue.trim()) return secret.secretValue.trim();
  for (const key of keys) {
    const value = secret.secretJson[key];
    if (typeof value === 'string' && value.trim()) return value.trim();
  }
  return '';
}

export function requireSecretString(config: TenantProviderConfig, keys: string[], code: string) {
  const value = secretString(config, keys);
  if (value) return value;
  throw new HttpError(503, `${config.provider} secret is missing ${keys[0]}`, code);
}
