import * as path from 'path';
import { Logger } from './logger';

/** CORS origin can be a string or RegExp for pattern matching (e.g., localhost with any port) */
export type CorsOrigin = string | RegExp;

/**
 * Parse CORS origin string into CorsOrigin type (string or RegExp).
 * Supports wildcard subdomain syntax: https://*.example.com
 * Converts wildcards to safe RegExp patterns.
 *
 * SECURITY: The generated pattern only allows alphanumeric characters and
 * hyphens in the subdomain portion to prevent domain confusion attacks.
 * For example, https://*.example.com will NOT match https://evil.com.example.com
 *
 * @param origin - CORS origin string (exact match or wildcard pattern)
 * @returns CorsOrigin (string for exact match, RegExp for wildcard)
 * @throws Error if wildcard pattern is invalid or unsafe
 */
export const parseCorsOrigin = (origin: string): CorsOrigin => {
  const trimmed = origin.trim();

  // Validate non-empty
  if (!trimmed) {
    throw new Error('CORS origin cannot be empty');
  }

  // No wildcard - return as-is for exact match
  if (!trimmed.includes('*')) {
    return trimmed;
  }

  // Validate wildcard count
  const wildcardCount = (trimmed.match(/\*/g) || []).length;
  if (wildcardCount > 1) {
    throw new Error(`Invalid CORS origin "${trimmed}": multiple wildcards not allowed`);
  }

  // Only allow subdomain wildcards: https://*.example.com
  const subdomainWildcardPattern = /^(https?):\/\/\*\.([a-z0-9.-]+)(:\d+)?$/i;
  const match = trimmed.match(subdomainWildcardPattern);

  if (!match) {
    throw new Error(
      `Invalid CORS origin "${trimmed}": wildcard only allowed as subdomain (e.g., https://*.example.com)`,
    );
  }

  const [, protocol, domain, port] = match;

  // Convert to safe RegExp: https://*.example.com -> /^https:\/\/[a-zA-Z0-9-]+\.example\.com$/i
  // Only allow alphanumeric and hyphens in subdomain (prevents domain confusion)
  // Normalize domain to lowercase (browsers send Origin header in lowercase per RFC 6454)
  const escapedDomain = domain.toLowerCase().replace(/\./g, '\\.');
  const portPart = port ? port.replace(/\./g, '\\.') : '';
  const pattern = `^${protocol}:\\/\\/[a-zA-Z0-9-]+\\.${escapedDomain}${portPart}$`;

  // Use case-insensitive flag to handle uppercase/lowercase variations
  return new RegExp(pattern, 'i');
};

export interface PrivacyConfig {
  contactName: string;
  addressStreet: string;
  addressCity: string;
  addressCountry: string;
  contactEmail: string;
}

export interface ServerConfig {
  port: number;
  host: string;
  dataDir: string;
  /**
   * Enables the batched upload implementation in SyncService.
   * Default false for staged rollout.
   */
  batchUpload: boolean;
  /**
   * Publicly reachable base URL used for links in emails.
   * Should point to the reverse-proxied address users can access.
   */
  publicUrl: string;
  cors: {
    enabled: boolean;
    allowedOrigins?: CorsOrigin[];
  };
  smtp?: {
    host: string;
    port: number;
    secure: boolean;
    user?: string;
    pass?: string;
    from: string;
  };
  /**
   * Privacy policy contact information.
   * Required for German legal compliance (Impressum).
   */
  privacy?: PrivacyConfig;
  /**
   * Test mode configuration. When enabled, provides endpoints for E2E testing.
   * NEVER enable in production!
   */
  testMode?: {
    enabled: boolean;
    /** Automatically verify users on registration (skip email verification) */
    autoVerifyUsers: boolean;
  };
}

/**
 * Default CORS origins for production security.
 * - Production app: exact match
 * - Preview deployments: RegExp pattern matching Cloudflare Pages format
 * Use CORS_ORIGINS env var to add additional origins.
 */
const DEFAULT_CORS_ORIGINS: CorsOrigin[] = [
  'https://app.super-productivity.com',
  // Cloudflare Pages preview format: <commit-hash>.<project>.pages.dev
  // Using [a-zA-Z0-9-]+ to prevent domain confusion attacks (e.g., evil.com.preview.pages.dev)
  /^https:\/\/[a-zA-Z0-9-]+\.super-productivity-preview\.pages\.dev$/,
];

const DEFAULT_CONFIG: ServerConfig = {
  port: 1900,
  host: '0.0.0.0',
  dataDir: './data',
  batchUpload: false,
  publicUrl: 'http://localhost:1900',
  cors: {
    enabled: true,
    allowedOrigins: DEFAULT_CORS_ORIGINS,
  },
};

/**
 * Load configuration from environment variables.
 * Environment variables take precedence over defaults.
 */
export const loadConfigFromEnv = (
  overrides: Partial<ServerConfig> = {},
): ServerConfig => {
  const config: ServerConfig = {
    ...DEFAULT_CONFIG,
    ...overrides,
    cors: {
      ...DEFAULT_CONFIG.cors,
      ...(overrides.cors || {}),
    },
  };

  // Override with environment variables
  if (process.env.PORT) {
    const parsedPort = parseInt(process.env.PORT, 10);
    if (Number.isInteger(parsedPort) && parsedPort > 0) {
      config.port = parsedPort;
    } else {
      throw new Error(`Invalid PORT: ${process.env.PORT}. Must be a positive integer.`);
    }
  }

  if (process.env.HOST !== undefined) {
    const trimmedHost = process.env.HOST.trim();
    if (!trimmedHost) {
      throw new Error('Invalid HOST: must not be empty.');
    }
    if (/\s/.test(trimmedHost)) {
      throw new Error(`Invalid HOST: ${process.env.HOST}. Must not contain whitespace.`);
    }
    if (/^https?:\/\//i.test(trimmedHost) || trimmedHost.includes('/')) {
      throw new Error(
        `Invalid HOST: ${process.env.HOST}. Use a hostname or IP address without protocol or path.`,
      );
    }
    config.host = trimmedHost;
  }

  if (process.env.DATA_DIR) {
    const resolvedPath = path.resolve(process.env.DATA_DIR);
    if (!resolvedPath) {
      throw new Error(`Invalid DATA_DIR: ${process.env.DATA_DIR}`);
    }
    config.dataDir = resolvedPath;
  } else {
    // Resolve default data dir relative to cwd
    config.dataDir = path.resolve(config.dataDir);
  }

  if (process.env.SUPERSYNC_BATCH_UPLOAD === 'true') {
    if (process.env.SUPERSYNC_PAYLOAD_BYTES_BACKFILL_COMPLETE !== 'true') {
      throw new Error(
        'SUPERSYNC_BATCH_UPLOAD=true requires SUPERSYNC_PAYLOAD_BYTES_BACKFILL_COMPLETE=true. ' +
          'Run `npm run migrate-payload-bytes` to backfill operation payload_bytes first.',
      );
    }
    config.batchUpload = true;
  } else if (process.env.SUPERSYNC_BATCH_UPLOAD !== undefined) {
    config.batchUpload = false;
  }

  // Public URL (for email links)
  if (process.env.PUBLIC_URL) {
    const trimmed = process.env.PUBLIC_URL.trim();
    if (!/^https?:\/\//i.test(trimmed)) {
      throw new Error('PUBLIC_URL must start with http:// or https://');
    }
    config.publicUrl = trimmed.replace(/\/+$/, '');
  } else {
    config.publicUrl = `http://localhost:${config.port}`;
  }

  // Enforce HTTPS for PUBLIC_URL in production
  if (process.env.NODE_ENV === 'production' && !config.publicUrl.startsWith('https://')) {
    throw new Error('PUBLIC_URL must use HTTPS in production');
  }

  // CORS configuration
  // CORS_ORIGINS overrides defaults (comma-separated list of origins)
  // Use CORS_ORIGINS=* for wildcard (NOT recommended for production)
  if (process.env.CORS_ENABLED !== undefined) {
    config.cors.enabled = process.env.CORS_ENABLED === 'true';
  }
  if (process.env.CORS_ORIGINS) {
    const origins = process.env.CORS_ORIGINS.split(',').map((o) => o.trim());

    // Block universal wildcard in production - security vulnerability
    if (origins.includes('*')) {
      if (process.env.NODE_ENV === 'production') {
        throw new Error(
          'CORS_ORIGINS wildcard (*) is not allowed in production. ' +
            'Specify explicit allowed origins for security.',
        );
      }
      Logger.warn(
        'CORS_ORIGINS contains wildcard (*). This is insecure and not recommended for production.',
      );
      // Parse non-wildcard origins, keep * as-is
      try {
        config.cors.allowedOrigins = origins.map((o) =>
          o === '*' ? o : parseCorsOrigin(o),
        );
      } catch (err) {
        throw new Error(
          `Invalid CORS_ORIGINS configuration: ${err instanceof Error ? err.message : String(err)}`,
        );
      }
    } else {
      // Parse each origin (converts wildcard patterns to RegExp)
      try {
        config.cors.allowedOrigins = origins.map(parseCorsOrigin);
      } catch (err) {
        throw new Error(
          `Invalid CORS_ORIGINS configuration: ${err instanceof Error ? err.message : String(err)}`,
        );
      }
    }
    // If origins are provided, implicitly enable CORS if not explicitly disabled
    if (process.env.CORS_ENABLED === undefined) {
      config.cors.enabled = true;
    }
  }

  // SMTP Configuration
  if (process.env.SMTP_HOST) {
    const port = parseInt(process.env.SMTP_PORT || '587', 10);
    config.smtp = {
      host: process.env.SMTP_HOST,
      port,
      secure:
        process.env.SMTP_SECURE !== undefined
          ? process.env.SMTP_SECURE === 'true'
          : port === 465,
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
      from: process.env.SMTP_FROM || '"SuperSync" <noreply@example.com>',
    };
  }

  // Privacy policy configuration (for German legal requirements)
  if (process.env.PRIVACY_CONTACT_NAME) {
    config.privacy = {
      contactName: process.env.PRIVACY_CONTACT_NAME,
      addressStreet: process.env.PRIVACY_ADDRESS_STREET || '',
      addressCity: process.env.PRIVACY_ADDRESS_CITY || '',
      addressCountry: process.env.PRIVACY_ADDRESS_COUNTRY || '',
      contactEmail: process.env.PRIVACY_CONTACT_EMAIL || '',
    };
  }

  // Test mode configuration
  // Requires both TEST_MODE=true AND TEST_MODE_CONFIRM=yes-i-understand-the-risks
  // This double-check prevents accidental test mode enablement
  if (process.env.TEST_MODE === 'true') {
    if (process.env.NODE_ENV === 'production') {
      throw new Error('TEST_MODE cannot be enabled in production');
    }
    if (process.env.TEST_MODE_CONFIRM !== 'yes-i-understand-the-risks') {
      throw new Error(
        'TEST_MODE requires TEST_MODE_CONFIRM=yes-i-understand-the-risks to prevent accidental enablement',
      );
    }
    Logger.warn(
      '⚠️  TEST_MODE is enabled - test routes are exposed. DO NOT use in production!',
    );
    config.testMode = {
      enabled: true,
      autoVerifyUsers: true,
    };
  }

  // Validation
  if (!Number.isInteger(config.port) || config.port <= 0) {
    throw new Error(`Invalid port configuration: ${config.port}`);
  }

  if (!config.host) {
    throw new Error('Host configuration is missing');
  }

  if (!config.dataDir) {
    throw new Error('Data directory configuration is missing');
  }

  return config;
};
