import * as plugins from '../plugins.js';

export interface RedirectRule {
  /**
   * Optional protocol to match (http or https). If not specified, matches both.
   */
  fromProtocol?: 'http' | 'https';
  
  /**
   * Optional hostname pattern to match. Can use * as wildcard.
   * If not specified, matches all hosts.
   */
  fromHost?: string;
  
  /**
   * Optional path prefix to match. If not specified, matches all paths.
   */
  fromPath?: string;
  
  /**
   * Target protocol for the redirect (http or https)
   */
  toProtocol: 'http' | 'https';
  
  /**
   * Target hostname for the redirect. Can use $1, $2, etc. to reference
   * captured groups from wildcard matches in fromHost.
   */
  toHost: string;
  
  /**
   * Optional target path prefix. If not specified, keeps original path.
   * Can use $path to reference the original path.
   */
  toPath?: string;
  
  /**
   * HTTP status code for the redirect (301 for permanent, 302 for temporary)
   */
  statusCode?: 301 | 302 | 307 | 308;
}

export class Redirect {
  private httpServer?: plugins.http.Server;
  private httpsServer?: plugins.https.Server;
  private rules: RedirectRule[] = [];
  private httpPort: number = 80;
  private httpsPort: number = 443;
  private sslOptions?: {
    key: Buffer;
    cert: Buffer;
  };

  /**
   * Create a new Redirect instance
   * @param options Configuration options
   */
  constructor(options: {
    httpPort?: number;
    httpsPort?: number;
    sslOptions?: {
      key: Buffer;
      cert: Buffer;
    };
    rules?: RedirectRule[];
  } = {}) {
    if (options.httpPort) this.httpPort = options.httpPort;
    if (options.httpsPort) this.httpsPort = options.httpsPort;
    if (options.sslOptions) this.sslOptions = options.sslOptions;
    if (options.rules) this.rules = options.rules;
  }

  /**
   * Add a redirect rule
   */
  public addRule(rule: RedirectRule): void {
    this.rules.push(rule);
  }

  /**
   * Remove all redirect rules
   */
  public clearRules(): void {
    this.rules = [];
  }

  /**
   * Set SSL options for HTTPS redirects
   */
  public setSslOptions(options: { key: Buffer; cert: Buffer }): void {
    this.sslOptions = options;
  }

  /**
   * Process a request according to the configured rules
   */
  private handleRequest(
    request: plugins.http.IncomingMessage,
    response: plugins.http.ServerResponse,
    protocol: 'http' | 'https'
  ): void {
    const requestUrl = new URL(
      request.url || '/',
      `${protocol}://${request.headers.host || 'localhost'}`
    );
    
    const host = requestUrl.hostname;
    const path = requestUrl.pathname + requestUrl.search;
    
    // Find matching rule
    const matchedRule = this.findMatchingRule(protocol, host, path);
    
    if (matchedRule) {
      const targetUrl = this.buildTargetUrl(matchedRule, host, path);
      
      console.log(`Redirecting ${protocol}://${host}${path} to ${targetUrl}`);
      
      response.writeHead(matchedRule.statusCode || 302, {
        Location: targetUrl,
      });
      response.end();
    } else {
      // No matching rule, send 404
      response.writeHead(404, { 'Content-Type': 'text/plain' });
      response.end('Not Found');
    }
  }

  /**
   * Find a matching redirect rule for the given request
   */
  private findMatchingRule(
    protocol: 'http' | 'https',
    host: string,
    path: string
  ): RedirectRule | undefined {
    return this.rules.find((rule) => {
      // Check protocol match
      if (rule.fromProtocol && rule.fromProtocol !== protocol) {
        return false;
      }

      // Check host match
      if (rule.fromHost) {
        const pattern = rule.fromHost.replace(/\*/g, '(.*)');
        const regex = new RegExp(`^${pattern}$`);
        if (!regex.test(host)) {
          return false;
        }
      }

      // Check path match
      if (rule.fromPath && !path.startsWith(rule.fromPath)) {
        return false;
      }

      return true;
    });
  }

  /**
   * Build the target URL for a redirect
   */
  private buildTargetUrl(rule: RedirectRule, originalHost: string, originalPath: string): string {
    let targetHost = rule.toHost;
    
    // Replace wildcards in host
    if (rule.fromHost && rule.fromHost.includes('*')) {
      const pattern = rule.fromHost.replace(/\*/g, '(.*)');
      const regex = new RegExp(`^${pattern}$`);
      const matches = originalHost.match(regex);
      
      if (matches) {
        for (let i = 1; i < matches.length; i++) {
          targetHost = targetHost.replace(`$${i}`, matches[i]);
        }
      }
    }
    
    // Build target path
    let targetPath = originalPath;
    if (rule.toPath) {
      if (rule.toPath.includes('$path')) {
        // Replace $path with original path, optionally removing the fromPath prefix
        const pathSuffix = rule.fromPath ? 
          originalPath.substring(rule.fromPath.length) : 
          originalPath;
        
        targetPath = rule.toPath.replace('$path', pathSuffix);
      } else {
        targetPath = rule.toPath;
      }
    }
    
    return `${rule.toProtocol}://${targetHost}${targetPath}`;
  }

  /**
   * Start the redirect server(s)
   */
  public async start(): Promise<void> {
    const tasks = [];

    // Create and start HTTP server if we have a port
    if (this.httpPort) {
      this.httpServer = plugins.http.createServer((req, res) => 
        this.handleRequest(req, res, 'http')
      );
      
      const httpStartPromise = new Promise<void>((resolve) => {
        this.httpServer?.listen(this.httpPort, () => {
          console.log(`HTTP redirect server started on port ${this.httpPort}`);
          resolve();
        });
      });
      
      tasks.push(httpStartPromise);
    }

    // Create and start HTTPS server if we have SSL options and a port
    if (this.httpsPort && this.sslOptions) {
      this.httpsServer = plugins.https.createServer(this.sslOptions, (req, res) => 
        this.handleRequest(req, res, 'https')
      );
      
      const httpsStartPromise = new Promise<void>((resolve) => {
        this.httpsServer?.listen(this.httpsPort, () => {
          console.log(`HTTPS redirect server started on port ${this.httpsPort}`);
          resolve();
        });
      });
      
      tasks.push(httpsStartPromise);
    }

    // Wait for all servers to start
    await Promise.all(tasks);
  }

  /**
   * Stop the redirect server(s)
   */
  public async stop(): Promise<void> {
    const tasks = [];

    if (this.httpServer) {
      const httpStopPromise = new Promise<void>((resolve) => {
        this.httpServer?.close(() => {
          console.log('HTTP redirect server stopped');
          resolve();
        });
      });
      tasks.push(httpStopPromise);
    }

    if (this.httpsServer) {
      const httpsStopPromise = new Promise<void>((resolve) => {
        this.httpsServer?.close(() => {
          console.log('HTTPS redirect server stopped');
          resolve();
        });
      });
      tasks.push(httpsStopPromise);
    }

    await Promise.all(tasks);
  }
}

// For backward compatibility
export class SslRedirect {
  private redirect: Redirect;
  port: number;

  constructor(portArg: number) {
    this.port = portArg;
    this.redirect = new Redirect({
      httpPort: portArg,
      rules: [{
        fromProtocol: 'http',
        toProtocol: 'https',
        toHost: '$1',
        statusCode: 302
      }]
    });
  }

  public async start() {
    await this.redirect.start();
  }

  public async stop() {
    await this.redirect.stop();
  }
}