/**
 * Platform Services Manager
 * Orchestrates platform services (MongoDB, MinIO) and their resources
 */

import type {
  IService,
  IPlatformService,
  IPlatformResource,
  IPlatformRequirements,
  IProvisionedResource,
  TPlatformServiceType,
} from '../../types.ts';
import type { IPlatformServiceProvider } from './providers/base.ts';
import { MongoDBProvider } from './providers/mongodb.ts';
import { MinioProvider } from './providers/minio.ts';
import { SmartProxyProvider } from './providers/smartproxy.ts';
import { ClickHouseProvider } from './providers/clickhouse.ts';
import { MariaDBProvider } from './providers/mariadb.ts';
import { RedisProvider } from './providers/redis.ts';
import { logger } from '../../logging.ts';
import { getErrorMessage } from '../../utils/error.ts';
import { credentialEncryption } from '../encryption.ts';
import type { Onebox } from '../onebox.ts';

export class PlatformServicesManager {
  private oneboxRef: Onebox;
  private providers = new Map<TPlatformServiceType, IPlatformServiceProvider>();

  constructor(oneboxRef: Onebox) {
    this.oneboxRef = oneboxRef;
  }

  /**
   * Initialize the platform services manager
   */
  async init(): Promise<void> {
    // Initialize encryption
    await credentialEncryption.init();

    // Register providers
    this.registerProvider(new MongoDBProvider(this.oneboxRef));
    this.registerProvider(new MinioProvider(this.oneboxRef));
    this.registerProvider(new SmartProxyProvider(this.oneboxRef));
    this.registerProvider(new ClickHouseProvider(this.oneboxRef));
    this.registerProvider(new MariaDBProvider(this.oneboxRef));
    this.registerProvider(new RedisProvider(this.oneboxRef));

    logger.info(`Platform services manager initialized with ${this.providers.size} providers`);
  }

  /**
   * Register a platform service provider
   */
  registerProvider(provider: IPlatformServiceProvider): void {
    this.providers.set(provider.type, provider);
    logger.debug(`Registered platform service provider: ${provider.displayName}`);
  }

  /**
   * Get a provider by type
   */
  getProvider(type: TPlatformServiceType): IPlatformServiceProvider | undefined {
    return this.providers.get(type);
  }

  /**
   * Get all registered providers
   */
  getAllProviders(): IPlatformServiceProvider[] {
    return Array.from(this.providers.values());
  }

  /**
   * Ensure a platform service is running, deploying it if necessary
   */
  async ensureRunning(type: TPlatformServiceType): Promise<IPlatformService> {
    const provider = this.providers.get(type);
    if (!provider) {
      throw new Error(`Unknown platform service type: ${type}`);
    }

    // Check if platform service exists in database
    let platformService = this.oneboxRef.database.getPlatformServiceByType(type);

    if (!platformService) {
      // Create platform service record
      logger.info(`Creating new ${provider.displayName} platform service...`);
      const config = provider.getDefaultConfig();

      platformService = this.oneboxRef.database.createPlatformService({
        name: `onebox-${type}`,
        type,
        status: 'stopped',
        config,
        createdAt: Date.now(),
        updatedAt: Date.now(),
      });
    }

    // Check if already running
    let needsDeploy = platformService.status !== 'running';

    if (platformService.status === 'running') {
      // Verify it's actually healthy
      const isHealthy = await provider.healthCheck();
      if (isHealthy) {
        logger.debug(`${provider.displayName} is already running and healthy`);
        return platformService;
      }
      logger.warn(`${provider.displayName} reports running but health check failed, will redeploy...`);
      // Mark status as needing redeploy - container may have been recreated with different credentials
      this.oneboxRef.database.updatePlatformService(platformService.id!, { status: 'stopped' });
      needsDeploy = true;
    }

    // Deploy if needed
    if (needsDeploy) {
      logger.info(`Starting ${provider.displayName} platform service...`);

      try {
        this.oneboxRef.database.updatePlatformService(platformService.id!, { status: 'starting' });

        const containerId = await provider.deployContainer();

        // Wait for health check to pass
        const healthy = await this.waitForHealthy(type, 60000); // 60 second timeout

        if (healthy) {
          this.oneboxRef.database.updatePlatformService(platformService.id!, {
            status: 'running',
            containerId,
          });
          logger.success(`${provider.displayName} platform service is now running`);
        } else {
          this.oneboxRef.database.updatePlatformService(platformService.id!, { status: 'failed' });
          throw new Error(`${provider.displayName} failed to start within timeout`);
        }

        // Refresh platform service from database
        platformService = this.oneboxRef.database.getPlatformServiceByType(type)!;
      } catch (error) {
        logger.error(`Failed to start ${provider.displayName}: ${getErrorMessage(error)}`);
        this.oneboxRef.database.updatePlatformService(platformService.id!, { status: 'failed' });
        throw error;
      }
    }

    return platformService;
  }

  /**
   * Wait for a platform service to become healthy
   */
  private async waitForHealthy(type: TPlatformServiceType, timeoutMs: number): Promise<boolean> {
    const provider = this.providers.get(type);
    if (!provider) {
      logger.warn(`waitForHealthy: no provider for type ${type}`);
      return false;
    }

    logger.info(`waitForHealthy: starting health check loop for ${type} (timeout: ${timeoutMs}ms)`);
    const startTime = Date.now();
    const checkInterval = 2000; // Check every 2 seconds
    let checkCount = 0;

    while (Date.now() - startTime < timeoutMs) {
      checkCount++;
      logger.info(`waitForHealthy: health check attempt #${checkCount} for ${type}`);
      const isHealthy = await provider.healthCheck();
      if (isHealthy) {
        logger.info(`waitForHealthy: ${type} became healthy after ${checkCount} attempts`);
        return true;
      }
      await new Promise((resolve) => setTimeout(resolve, checkInterval));
    }

    logger.warn(`waitForHealthy: ${type} did not become healthy after ${checkCount} attempts (${timeoutMs}ms)`);
    return false;
  }

  /**
   * Stop a platform service
   */
  async stopPlatformService(type: TPlatformServiceType): Promise<void> {
    const provider = this.providers.get(type);
    if (!provider) {
      throw new Error(`Unknown platform service type: ${type}`);
    }

    const platformService = this.oneboxRef.database.getPlatformServiceByType(type);
    if (!platformService) {
      logger.warn(`Platform service ${type} not found`);
      return;
    }

    if (!platformService.containerId) {
      logger.warn(`Platform service ${type} has no container ID`);
      return;
    }

    logger.info(`Stopping ${provider.displayName} platform service...`);
    this.oneboxRef.database.updatePlatformService(platformService.id!, { status: 'stopping' });

    try {
      await provider.stopContainer(platformService.containerId);
      this.oneboxRef.database.updatePlatformService(platformService.id!, {
        status: 'stopped',
        containerId: undefined,
      });
      logger.success(`${provider.displayName} platform service stopped`);
    } catch (error) {
      logger.error(`Failed to stop ${provider.displayName}: ${getErrorMessage(error)}`);
      this.oneboxRef.database.updatePlatformService(platformService.id!, { status: 'failed' });
      throw error;
    }
  }

  /**
   * Provision platform resources for a user service based on its requirements
   */
  async provisionForService(service: IService): Promise<Record<string, string>> {
    const requirements = service.platformRequirements;
    if (!requirements) {
      return {};
    }

    const allEnvVars: Record<string, string> = {};

    // Provision MongoDB if requested
    if (requirements.mongodb) {
      logger.info(`Provisioning MongoDB for service '${service.name}'...`);

      // Ensure MongoDB is running
      const mongoService = await this.ensureRunning('mongodb');
      const provider = this.providers.get('mongodb')!;

      // Provision database
      const result = await provider.provisionResource(service);

      // Store resource record
      const encryptedCreds = await credentialEncryption.encrypt(result.credentials);
      this.oneboxRef.database.createPlatformResource({
        platformServiceId: mongoService.id!,
        serviceId: service.id!,
        resourceType: result.type,
        resourceName: result.name,
        credentialsEncrypted: encryptedCreds,
        createdAt: Date.now(),
      });

      // Merge env vars
      Object.assign(allEnvVars, result.envVars);
      logger.success(`MongoDB provisioned for service '${service.name}'`);
    }

    // Provision S3/MinIO if requested
    if (requirements.s3) {
      logger.info(`Provisioning S3 storage for service '${service.name}'...`);

      // Ensure MinIO is running
      const minioService = await this.ensureRunning('minio');
      const provider = this.providers.get('minio')!;

      // Provision bucket
      const result = await provider.provisionResource(service);

      // Store resource record
      const encryptedCreds = await credentialEncryption.encrypt(result.credentials);
      this.oneboxRef.database.createPlatformResource({
        platformServiceId: minioService.id!,
        serviceId: service.id!,
        resourceType: result.type,
        resourceName: result.name,
        credentialsEncrypted: encryptedCreds,
        createdAt: Date.now(),
      });

      // Merge env vars
      Object.assign(allEnvVars, result.envVars);
      logger.success(`S3 storage provisioned for service '${service.name}'`);
    }

    // Provision ClickHouse if requested
    if (requirements.clickhouse) {
      logger.info(`Provisioning ClickHouse for service '${service.name}'...`);

      // Ensure ClickHouse is running
      const clickhouseService = await this.ensureRunning('clickhouse');
      const provider = this.providers.get('clickhouse')!;

      // Provision database
      const result = await provider.provisionResource(service);

      // Store resource record
      const encryptedCreds = await credentialEncryption.encrypt(result.credentials);
      this.oneboxRef.database.createPlatformResource({
        platformServiceId: clickhouseService.id!,
        serviceId: service.id!,
        resourceType: result.type,
        resourceName: result.name,
        credentialsEncrypted: encryptedCreds,
        createdAt: Date.now(),
      });

      // Merge env vars
      Object.assign(allEnvVars, result.envVars);
      logger.success(`ClickHouse provisioned for service '${service.name}'`);
    }

    // Provision Redis if requested
    if (requirements.redis) {
      logger.info(`Provisioning Redis for service '${service.name}'...`);

      // Ensure Redis is running
      const redisService = await this.ensureRunning('redis');
      const provider = this.providers.get('redis')!;

      // Provision cache resource
      const result = await provider.provisionResource(service);

      // Store resource record
      const encryptedCreds = await credentialEncryption.encrypt(result.credentials);
      this.oneboxRef.database.createPlatformResource({
        platformServiceId: redisService.id!,
        serviceId: service.id!,
        resourceType: result.type,
        resourceName: result.name,
        credentialsEncrypted: encryptedCreds,
        createdAt: Date.now(),
      });

      // Merge env vars
      Object.assign(allEnvVars, result.envVars);
      logger.success(`Redis provisioned for service '${service.name}'`);
    }

    // Provision MariaDB if requested
    if (requirements.mariadb) {
      logger.info(`Provisioning MariaDB for service '${service.name}'...`);

      // Ensure MariaDB is running
      const mariadbService = await this.ensureRunning('mariadb');
      const provider = this.providers.get('mariadb')!;

      // Provision database
      const result = await provider.provisionResource(service);

      // Store resource record
      const encryptedCreds = await credentialEncryption.encrypt(result.credentials);
      this.oneboxRef.database.createPlatformResource({
        platformServiceId: mariadbService.id!,
        serviceId: service.id!,
        resourceType: result.type,
        resourceName: result.name,
        credentialsEncrypted: encryptedCreds,
        createdAt: Date.now(),
      });

      // Merge env vars
      Object.assign(allEnvVars, result.envVars);
      logger.success(`MariaDB provisioned for service '${service.name}'`);
    }

    return allEnvVars;
  }

  /**
   * Cleanup platform resources when a user service is deleted
   */
  async cleanupForService(serviceId: number): Promise<void> {
    const resources = this.oneboxRef.database.getPlatformResourcesByService(serviceId);

    for (const resource of resources) {
      try {
        const platformService = this.oneboxRef.database.getPlatformServiceById(resource.platformServiceId);
        if (!platformService) {
          logger.warn(`Platform service not found for resource ${resource.id}`);
          continue;
        }

        const provider = this.providers.get(platformService.type);
        if (!provider) {
          logger.warn(`Provider not found for type ${platformService.type}`);
          continue;
        }

        // Decrypt credentials
        const credentials = await credentialEncryption.decrypt(resource.credentialsEncrypted);

        // Deprovision the resource
        logger.info(`Cleaning up ${resource.resourceType} '${resource.resourceName}'...`);
        await provider.deprovisionResource(resource, credentials);

        // Delete resource record
        this.oneboxRef.database.deletePlatformResource(resource.id!);
        logger.success(`Cleaned up ${resource.resourceType} '${resource.resourceName}'`);
      } catch (error) {
        logger.error(`Failed to cleanup resource ${resource.id}: ${getErrorMessage(error)}`);
        // Continue with other resources even if one fails
      }
    }
  }

  /**
   * Get injected environment variables for a service
   */
  async getInjectedEnvVars(serviceId: number): Promise<Record<string, string>> {
    const resources = this.oneboxRef.database.getPlatformResourcesByService(serviceId);
    const allEnvVars: Record<string, string> = {};

    for (const resource of resources) {
      const platformService = this.oneboxRef.database.getPlatformServiceById(resource.platformServiceId);
      if (!platformService) continue;

      const provider = this.providers.get(platformService.type);
      if (!provider) continue;

      const credentials = await credentialEncryption.decrypt(resource.credentialsEncrypted);
      const mappings = provider.getEnvVarMappings();

      for (const mapping of mappings) {
        if (credentials[mapping.credentialPath]) {
          allEnvVars[mapping.envVar] = credentials[mapping.credentialPath];
        }
      }
    }

    return allEnvVars;
  }

  /**
   * Get all platform services with their status
   */
  getAllPlatformServices(): IPlatformService[] {
    return this.oneboxRef.database.getAllPlatformServices();
  }

  /**
   * Get resources for a specific user service
   */
  async getResourcesForService(serviceId: number): Promise<Array<{
    resource: IPlatformResource;
    platformService: IPlatformService;
    credentials: Record<string, string>;
  }>> {
    const resources = this.oneboxRef.database.getPlatformResourcesByService(serviceId);
    const result = [];

    for (const resource of resources) {
      const platformService = this.oneboxRef.database.getPlatformServiceById(resource.platformServiceId);
      if (!platformService) continue;

      const credentials = await credentialEncryption.decrypt(resource.credentialsEncrypted);

      result.push({
        resource,
        platformService,
        credentials,
      });
    }

    return result;
  }
}
