import baseDebug from '@tryghost/debug';
import logging from '@tryghost/logging';
import {GhostInstance, MySQLManager} from './service-managers';
import {GhostManager} from './service-managers/ghost-manager';
import {randomUUID} from 'crypto';
import type {EgressMonitor} from './service-managers/egress-monitor';
import type {GhostConfig} from '@/helpers/playwright/fixture';

const debug = baseDebug('e2e:EnvironmentManager');

/**
 * Environment modes for E2E testing.
 * 
 * - dev: Uses dev infrastructure with hot-reloading dev servers
 * - build: Uses pre-built image (local or registry, controlled by GHOST_E2E_IMAGE)
 */
export type EnvironmentMode = 'dev' | 'build';
type GhostEnvOverrides = GhostConfig | Record<string, string>;

/**
 * Orchestrates e2e test environment.
 * 
 * Supports two modes controlled by GHOST_E2E_MODE environment variable:
 * - dev: Uses dev infrastructure with hot-reloading
 * - build: Uses pre-built image (set GHOST_E2E_IMAGE for registry images)
 * 
 * All modes use the same core infrastructure (MySQL, Redis, Mailpit) started
 * via docker compose. Analytics/Tinybird services are optional. Ghost and
 * gateway containers are created dynamically per-worker for test isolation.
 */
export class EnvironmentManager {
    private readonly mode: EnvironmentMode;
    private readonly workerIndex: number;
    private readonly mysql: MySQLManager;
    private readonly ghost: GhostManager;
    private initialized = false;

    constructor() {
        this.mode = this.detectMode();
        this.workerIndex = parseInt(process.env.TEST_PARALLEL_INDEX || '0', 10);
        
        this.mysql = new MySQLManager();
        this.ghost = new GhostManager({
            workerIndex: this.workerIndex,
            mode: this.mode
        });
    }

    /**
     * Detect environment mode from GHOST_E2E_MODE environment variable.
     */
    private detectMode(): EnvironmentMode {
        const envMode = process.env.GHOST_E2E_MODE;
        if (envMode === 'build' || envMode === 'dev') {
            return envMode;
        }

        logging.warn('GHOST_E2E_MODE is not set; defaulting to build mode. Use the e2e shell entrypoints for automatic mode resolution.');
        return 'build';
    }

    /**
     * Global setup - creates database snapshot for test isolation.
     * 
     * Creates the worker 0 containers (Ghost + Gateway) and waits for Ghost to
     * become healthy. Ghost automatically runs migrations on startup. Once healthy,
     * we snapshot the database for test isolation.
     */
    async globalSetup(): Promise<void> {
        logging.info(`Starting ${this.mode} environment global setup...`);

        await this.cleanupResources();

        // Create base database
        await this.mysql.recreateBaseDatabase('ghost_e2e_base');

        // Create containers and wait for Ghost to be healthy (runs migrations)
        await this.ghost.setup('ghost_e2e_base');
        await this.ghost.waitForReady();
        this.initialized = true;

        // Snapshot the migrated database for test isolation
        await this.mysql.createSnapshot('ghost_e2e_base');

        logging.info(`${this.mode} environment global setup complete`);
    }

    /**
     * Global teardown - cleanup resources.
     */
    async globalTeardown(): Promise<void> {
        if (this.shouldPreserveEnvironment()) {
            logging.info('PRESERVE_ENV is set - skipping teardown');
            return;
        }

        logging.info(`Starting ${this.mode} environment global teardown...`);
        await this.cleanupResources();
        logging.info(`${this.mode} environment global teardown complete`);
    }

    /**
     * Per-test setup - creates containers on first call, then clones database and restarts Ghost.
     */
    async perTestSetup(options: {
        config?: GhostEnvOverrides;
        stripe?: {
            secretKey: string;
            publishableKey: string;
        };
    } = {}): Promise<GhostInstance> {
        // Lazy initialization of Ghost containers (once per worker)
        if (!this.initialized) {
            debug('Initializing Ghost containers for worker', this.workerIndex, 'in mode', this.mode);
            await this.ghost.setup();
            this.initialized = true;
        }

        const siteUuid = randomUUID();
        const instanceId = `ghost_e2e_${siteUuid.replace(/-/g, '_')}`;

        // Setup database
        await this.mysql.setupTestDatabase(instanceId, siteUuid, {
            stripe: options.stripe
        });

        // Restart Ghost with new database
        await this.ghost.restartWithDatabase(instanceId, options.config);
        await this.ghost.waitForReady();

        const port = this.ghost.getGatewayPort();

        return {
            containerId: this.ghost.ghostContainerId!,
            instanceId,
            database: instanceId,
            port,
            baseUrl: `http://localhost:${port}`,
            siteUuid
        };
    }

    /**
     * Per-test teardown - drops test database.
     */
    async perTestTeardown(instance: GhostInstance): Promise<void> {
        await this.mysql.cleanupTestDatabase(instance.database);
    }

    /**
     * Egress monitor for this worker, or null when disabled / unavailable.
     * Records external hostnames the Ghost container resolves.
     */
    getEgressMonitor(): EgressMonitor | null {
        return this.ghost.getEgressMonitor();
    }

    private async cleanupResources(): Promise<void> {
        logging.info('Cleaning up e2e resources...');
        await this.ghost.cleanupAllContainers();
        await this.mysql.dropAllTestDatabases();
        await this.mysql.deleteSnapshot();
        logging.info('E2E resources cleaned up');
    }

    private shouldPreserveEnvironment(): boolean {
        return process.env.PRESERVE_ENV === 'true';
    }
}
