/**
 * Registry Manager for Onebox
 *
 * Manages Docker registry credentials and authentication
 */

import * as plugins from '../plugins.ts';
import type { IRegistry } from '../types.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts';
import { credentialEncryption } from './encryption.ts';

const encryptedPasswordPrefix = 'enc:v1:';

export class OneboxRegistriesManager {
  private oneboxRef: any; // Will be Onebox instance
  private database: OneboxDatabase;

  constructor(oneboxRef: any) {
    this.oneboxRef = oneboxRef;
    this.database = oneboxRef.database;
  }

  /**
   * Encrypt a password (simple base64 for now, should use proper encryption)
   */
  private async encryptPassword(password: string): Promise<string> {
    const encrypted = await credentialEncryption.encrypt({ password });
    return `${encryptedPasswordPrefix}${encrypted}`;
  }

  /**
   * Decrypt a password
   */
  private async decryptPassword(encrypted: string): Promise<string> {
    if (encrypted.startsWith(encryptedPasswordPrefix)) {
      const decrypted = await credentialEncryption.decrypt<{ password: string }>(
        encrypted.slice(encryptedPasswordPrefix.length),
      );
      return decrypted.password;
    }

    // Legacy compatibility for older databases that stored base64-encoded passwords.
    return new TextDecoder().decode(plugins.encoding.decodeBase64(encrypted));
  }

  /**
   * Add a registry
   */
  async addRegistry(url: string, username: string, password: string): Promise<IRegistry> {
    try {
      // Check if registry already exists
      const existing = this.database.getRegistryByURL(url);
      if (existing) {
        throw new Error(`Registry already exists: ${url}`);
      }

      // Encrypt password
      const passwordEncrypted = await this.encryptPassword(password);

      // Create registry in database
      const registry = await this.database.createRegistry({
        url,
        username,
        passwordEncrypted,
        createdAt: Date.now(),
      });

      logger.success(`Registry added: ${url}`);

      // Perform Docker login
      await this.loginToRegistry(registry);

      return registry;
    } catch (error) {
      logger.error(`Failed to add registry ${url}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Remove a registry
   */
  async removeRegistry(url: string): Promise<void> {
    try {
      const registry = this.database.getRegistryByURL(url);
      if (!registry) {
        throw new Error(`Registry not found: ${url}`);
      }

      this.database.deleteRegistry(url);
      logger.success(`Registry removed: ${url}`);

      // Note: We don't perform docker logout as it might affect other users
    } catch (error) {
      logger.error(`Failed to remove registry ${url}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * List all registries
   */
  listRegistries(): IRegistry[] {
    return this.database.getAllRegistries();
  }

  /**
   * Get registry by URL
   */
  getRegistry(url: string): IRegistry | null {
    return this.database.getRegistryByURL(url);
  }

  /**
   * Perform Docker login for a registry
   */
  async loginToRegistry(registry: IRegistry): Promise<void> {
    try {
      logger.info(`Logging into registry: ${registry.url}`);

      const password = await this.decryptPassword(registry.passwordEncrypted);

      // Use docker login command
      const command = [
        'docker',
        'login',
        registry.url,
        '--username',
        registry.username,
        '--password-stdin',
      ];

      const process = new Deno.Command('docker', {
        args: ['login', registry.url, '--username', registry.username, '--password-stdin'],
        stdin: 'piped',
        stdout: 'piped',
        stderr: 'piped',
      });

      const child = process.spawn();

      // Write password to stdin
      const writer = child.stdin.getWriter();
      await writer.write(new TextEncoder().encode(password));
      await writer.close();

      const { code, stdout, stderr } = await child.output();

      if (code !== 0) {
        const errorMsg = new TextDecoder().decode(stderr);
        throw new Error(`Docker login failed: ${errorMsg}`);
      }

      logger.success(`Logged into registry: ${registry.url}`);
    } catch (error) {
      logger.error(`Failed to login to registry ${registry.url}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Login to all registries (useful on daemon start)
   */
  async loginToAllRegistries(): Promise<void> {
    const registries = this.listRegistries();

    for (const registry of registries) {
      try {
        await this.loginToRegistry(registry);
      } catch (error) {
        logger.warn(`Failed to login to ${registry.url}: ${getErrorMessage(error)}`);
        // Continue with other registries
      }
    }
  }

  /**
   * Test registry connection
   */
  async testRegistry(url: string, username: string, password: string): Promise<boolean> {
    try {
      const command = new Deno.Command('docker', {
        args: ['login', url, '--username', username, '--password-stdin'],
        stdin: 'piped',
        stdout: 'piped',
        stderr: 'piped',
      });

      const child = command.spawn();

      const writer = child.stdin.getWriter();
      await writer.write(new TextEncoder().encode(password));
      await writer.close();

      const { code } = await child.output();

      return code === 0;
    } catch (error) {
      logger.error(`Failed to test registry ${url}: ${getErrorMessage(error)}`);
      return false;
    }
  }
}
