/**
 * MariaDB Platform Service Provider
 */

import { BasePlatformServiceProvider } from './base.ts';
import type {
  IService,
  IPlatformResource,
  IPlatformServiceConfig,
  IProvisionedResource,
  IEnvVarMapping,
  TPlatformServiceType,
  TPlatformResourceType,
} from '../../../types.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 MariaDBProvider extends BasePlatformServiceProvider {
  readonly type: TPlatformServiceType = 'mariadb';
  readonly displayName = 'MariaDB';
  readonly resourceTypes: TPlatformResourceType[] = ['database'];

  constructor(oneboxRef: Onebox) {
    super(oneboxRef);
  }

  getDefaultConfig(): IPlatformServiceConfig {
    return {
      image: 'mariadb:11',
      port: 3306,
      volumes: [`${this.getPlatformDataDir('mariadb')}:/var/lib/mysql`],
      environment: {
        MARIADB_ROOT_PASSWORD: '',
        // Password will be generated and stored encrypted
      },
    };
  }

  getEnvVarMappings(): IEnvVarMapping[] {
    return [
      { envVar: 'MARIADB_HOST', credentialPath: 'host' },
      { envVar: 'MARIADB_PORT', credentialPath: 'port' },
      { envVar: 'MARIADB_DATABASE', credentialPath: 'database' },
      { envVar: 'MARIADB_USER', credentialPath: 'username' },
      { envVar: 'MARIADB_PASSWORD', credentialPath: 'password' },
      { envVar: 'MARIADB_URI', credentialPath: 'connectionString' },
    ];
  }

  async deployContainer(): Promise<string> {
    const config = this.getDefaultConfig();
    const containerName = this.getContainerName();
    const dataDir = this.getPlatformDataDir('mariadb');

    logger.info(`Deploying MariaDB platform service as ${containerName}...`);

    // Check if we have existing data and stored credentials
    const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
    let adminCredentials: { username: string; password: string };
    let dataExists = false;

    // Check if data directory has existing MariaDB data
    try {
      const stat = await Deno.stat(`${dataDir}/ibdata1`);
      dataExists = stat.isFile;
      logger.info(`MariaDB data directory exists with ibdata1 file`);
    } catch {
      // ibdata1 file doesn't exist, this is a fresh install
      dataExists = false;
    }

    if (dataExists && platformService?.adminCredentialsEncrypted) {
      // Reuse existing credentials from database
      logger.info('Reusing existing MariaDB credentials (data directory already initialized)');
      adminCredentials = await credentialEncryption.decrypt<{ username: string; password: string }>(
        platformService.adminCredentialsEncrypted,
      );
    } else {
      // Generate new credentials for fresh deployment
      logger.info('Generating new MariaDB admin credentials');
      adminCredentials = {
        username: 'root',
        password: credentialEncryption.generatePassword(32),
      };

      // If data exists but we don't have credentials, we need to wipe the data
      if (dataExists) {
        logger.warn('MariaDB data exists but no credentials in database - wiping data directory');
        try {
          await Deno.remove(dataDir, { recursive: true });
        } catch (e) {
          logger.error(`Failed to wipe MariaDB data directory: ${getErrorMessage(e)}`);
          throw new Error('Cannot deploy MariaDB: data directory exists without credentials');
        }
      }
    }

    // Ensure data directory exists
    try {
      await Deno.mkdir(dataDir, { recursive: true });
    } catch (e) {
      // Directory might already exist
      if (!(e instanceof Deno.errors.AlreadyExists)) {
        logger.warn(`Could not create MariaDB data directory: ${getErrorMessage(e)}`);
      }
    }

    // Create container using Docker API
    const envVars = [
      `MARIADB_ROOT_PASSWORD=${adminCredentials.password}`,
    ];

    // Use Docker to create the container
    const containerId = await this.oneboxRef.docker.createPlatformContainer({
      name: containerName,
      image: config.image,
      port: config.port,
      env: envVars,
      volumes: config.volumes,
      network: this.getNetworkName(),
    });

    // Store encrypted admin credentials (only update if new or changed)
    const encryptedCreds = await credentialEncryption.encrypt(adminCredentials);
    if (platformService) {
      this.oneboxRef.database.updatePlatformService(platformService.id!, {
        containerId,
        adminCredentialsEncrypted: encryptedCreds,
        status: 'starting',
      });
    }

    logger.success(`MariaDB container created: ${containerId}`);
    return containerId;
  }

  async stopContainer(containerId: string): Promise<void> {
    logger.info(`Stopping MariaDB container ${containerId}...`);
    await this.oneboxRef.docker.stopContainer(containerId);
    logger.success('MariaDB container stopped');
  }

  async healthCheck(): Promise<boolean> {
    try {
      logger.info('MariaDB health check: starting...');
      const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
      if (!platformService) {
        logger.info('MariaDB health check: platform service not found in database');
        return false;
      }
      if (!platformService.adminCredentialsEncrypted) {
        logger.info('MariaDB health check: no admin credentials stored');
        return false;
      }
      if (!platformService.containerId) {
        logger.info('MariaDB health check: no container ID in database record');
        return false;
      }

      logger.info(`MariaDB health check: using container ID ${platformService.containerId.substring(0, 12)}...`);
      const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);

      // Use docker exec to run health check inside the container
      const result = await this.oneboxRef.docker.execInContainer(
        platformService.containerId,
        ['mariadb-admin', 'ping', '-u', 'root', `-p${adminCreds.password}`]
      );

      if (result.exitCode === 0) {
        logger.info('MariaDB health check: success');
        return true;
      } else {
        logger.info(`MariaDB health check failed: exit code ${result.exitCode}, stderr: ${result.stderr.substring(0, 200)}`);
        return false;
      }
    } catch (error) {
      logger.info(`MariaDB health check exception: ${getErrorMessage(error)}`);
      return false;
    }
  }

  async provisionResource(userService: IService): Promise<IProvisionedResource> {
    const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
    if (!platformService || !platformService.adminCredentialsEncrypted || !platformService.containerId) {
      throw new Error('MariaDB platform service not found or not configured');
    }

    const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);
    const containerName = this.getContainerName();

    // Generate resource names and credentials
    const dbName = this.generateResourceName(userService.name);
    const username = this.generateResourceName(userService.name);
    const password = credentialEncryption.generatePassword(32);

    logger.info(`Provisioning MariaDB database '${dbName}' for service '${userService.name}'...`);

    // Create database and user via mariadb inside the container
    const sql = [
      `CREATE DATABASE IF NOT EXISTS \`${dbName}\`;`,
      `CREATE USER IF NOT EXISTS '${username}'@'%' IDENTIFIED BY '${password.replace(/'/g, "\\'")}';`,
      `GRANT ALL PRIVILEGES ON \`${dbName}\`.* TO '${username}'@'%';`,
      `FLUSH PRIVILEGES;`,
    ].join(' ');

    const result = await this.oneboxRef.docker.execInContainer(
      platformService.containerId,
      [
        'mariadb',
        '-u', 'root',
        `-p${adminCreds.password}`,
        '-e', sql,
      ]
    );

    if (result.exitCode !== 0) {
      throw new Error(`Failed to provision MariaDB database: exit code ${result.exitCode}, output: ${result.stdout.substring(0, 200)} ${result.stderr.substring(0, 200)}`);
    }

    logger.success(`MariaDB database '${dbName}' provisioned with user '${username}'`);

    // Build the credentials and env vars
    const credentials: Record<string, string> = {
      host: containerName,
      port: '3306',
      database: dbName,
      username,
      password,
      connectionString: `mysql://${username}:${password}@${containerName}:3306/${dbName}`,
    };

    // Map credentials to env vars
    const envVars: Record<string, string> = {};
    for (const mapping of this.getEnvVarMappings()) {
      if (credentials[mapping.credentialPath]) {
        envVars[mapping.envVar] = credentials[mapping.credentialPath];
      }
    }

    return {
      type: 'database',
      name: dbName,
      credentials,
      envVars,
    };
  }

  async deprovisionResource(resource: IPlatformResource, credentials: Record<string, string>): Promise<void> {
    const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
    if (!platformService || !platformService.adminCredentialsEncrypted || !platformService.containerId) {
      throw new Error('MariaDB platform service not found or not configured');
    }

    const adminCreds = await credentialEncryption.decrypt(platformService.adminCredentialsEncrypted);

    logger.info(`Deprovisioning MariaDB database '${resource.resourceName}'...`);

    const sql = [
      `DROP USER IF EXISTS '${credentials.username}'@'%';`,
      `DROP DATABASE IF EXISTS \`${resource.resourceName}\`;`,
    ].join(' ');

    const result = await this.oneboxRef.docker.execInContainer(
      platformService.containerId,
      [
        'mariadb',
        '-u', 'root',
        `-p${adminCreds.password}`,
        '-e', sql,
      ]
    );

    if (result.exitCode !== 0) {
      logger.warn(`MariaDB deprovision returned exit code ${result.exitCode}: ${result.stderr.substring(0, 200)}`);
    }

    logger.success(`MariaDB database '${resource.resourceName}' dropped`);
  }
}
