import * as fs from 'fs';
import * as http from 'http';
import * as path from 'path';

export interface IFixtureServer {
  start(port?: number): Promise<{ url: string; port: number }>;
  stop(): Promise<void>;
  getFixtureUrl(stepNumber: number): string;
  isRunning(): boolean;
}

/**
 * Step number to fixture file mapping.
 * Steps 1-18 map to the corresponding HTML fixture files.
 */
const STEP_TO_FIXTURE: Record<number, string> = {
  1: '01-hosted-racing.html',
  2: '02-create-a-race.html',
  3: '03-race-information.html',
  4: '04-server-details.html',
  5: '05-set-admins.html',
  6: '06-add-an-admin.html',
  7: '07-time-limits.html',
  8: '08-set-cars.html',
  9: '09-add-a-car.html',
  10: '10-set-car-classes.html',
  11: '11-set-track.html',
  12: '12-add-a-track.html',
  13: '13-track-options.html',
  14: '14-time-of-day.html',
  15: '15-weather.html',
  16: '16-race-options.html',
  17: '17-team-driving.html',
  18: '18-track-conditions.html',
};

export class FixtureServer implements IFixtureServer {
  private server: http.Server | null = null;
  private port: number = 3456;
  private fixturesPath: string;

  constructor(fixturesPath?: string) {
    this.fixturesPath =
      fixturesPath ?? path.resolve(process.cwd(), 'html-dumps/iracing-hosted-sessions');
  }

  async start(port: number = 3456): Promise<{ url: string; port: number }> {
    if (this.server) {
      return { url: `http://localhost:${this.port}`, port: this.port };
    }

    this.port = port;

    return new Promise((resolve, reject) => {
      this.server = http.createServer((req, res) => {
        this.handleRequest(req, res);
      });

      this.server.on('error', (err: NodeJS.ErrnoException) => {
        if (err.code === 'EADDRINUSE') {
          this.server = null;
          this.start(port + 1).then(resolve).catch(reject);
        } else {
          reject(err);
        }
      });

      this.server.listen(this.port, () => {
        resolve({ url: `http://localhost:${this.port}`, port: this.port });
      });
    });
  }

  async stop(): Promise<void> {
    if (!this.server) {
      return;
    }

    return new Promise((resolve, reject) => {
      this.server!.close((err) => {
        if (err) {
          reject(err);
        } else {
          this.server = null;
          resolve();
        }
      });
    });
  }

  getFixtureUrl(stepNumber: number): string {
    const fixture = STEP_TO_FIXTURE[stepNumber];
    if (!fixture) {
      return `http://localhost:${this.port}/`;
    }
    return `http://localhost:${this.port}/${fixture}`;
  }

  isRunning(): boolean {
    return this.server !== null;
  }

  private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
    const urlPath = req.url || '/';

    let fileName: string;
    if (urlPath === '/') {
      fileName = STEP_TO_FIXTURE[1] ?? '01-hosted-racing.html';
    } else {
      fileName = urlPath.replace(/^\//, '');

      const legacyMatch = fileName.match(/^step-(\d+)-/);
      if (legacyMatch) {
        const stepNum = Number(legacyMatch[1]);
        const mapped = STEP_TO_FIXTURE[stepNum];
        if (mapped) {
          fileName = mapped;
        }
      }
    }

    const filePath = path.join(this.fixturesPath, fileName);

    // Security check - prevent directory traversal
    if (!filePath.startsWith(this.fixturesPath)) {
      res.writeHead(403, { 'Content-Type': 'text/plain' });
      res.end('Forbidden');
      return;
    }

    fs.readFile(filePath, (err, data) => {
      if (err) {
        const errno = (err as NodeJS.ErrnoException).code;
        if (errno === 'ENOENT') {
          res.writeHead(404, { 'Content-Type': 'text/plain' });
          res.end('Not Found');
        } else {
          res.writeHead(500, { 'Content-Type': 'text/plain' });
          res.end('Internal Server Error');
        }
        return;
      }

      const ext = path.extname(filePath).toLowerCase();
      const contentTypes: Record<string, string> = {
        '.html': 'text/html',
        '.css': 'text/css',
        '.js': 'application/javascript',
        '.png': 'image/png',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.gif': 'image/gif',
        '.svg': 'image/svg+xml',
      };

      const contentType = contentTypes[ext] || 'application/octet-stream';
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(data);
    });
  }
}

/**
 * Get the fixture filename for a given step number.
 */
export function getFixtureForStep(stepNumber: number): string | undefined {
  return STEP_TO_FIXTURE[stepNumber];
}

/**
 * Get all step-to-fixture mappings.
 */
export function getAllStepFixtureMappings(): Record<number, string> {
  return { ...STEP_TO_FIXTURE };
}