/**
 * Production-Grade Deployment Orchestrator
 *
 * Features:
 * - Complete deployment lifecycle management
 * - Idempotency checks at every stage
 * - Automatic rollback on failure
 * - Health verification
 * - Comprehensive logging
 * - State tracking
 */

import { DokployProductionClient } from '../api/dokploy-production.js';
import { createTranslator, type BackendLanguage } from '../lib/i18n-backend.js';

export interface DeploymentConfig {
  stackName: string;
  dockerImage: string;
  domainSuffix: string;
  port?: number;
  healthCheckTimeout?: number;
  healthCheckInterval?: number;
  registryId?: string;
  sharedProjectId?: string;
  sharedEnvironmentId?: string;
  lang?: string;
}

export interface DeploymentState {
  id: string;
  stackName: string;
  phase:
    | 'initializing'
    | 'creating_project'
    | 'getting_environment'
    | 'creating_application'
    | 'configuring_application'
    | 'creating_domain'
    | 'deploying'
    | 'verifying_health'
    | 'completed'
    | 'failed'
    | 'rolling_back';
  status: 'success' | 'failure' | 'in_progress';
  progress: number;
  message: string;
  url?: string;
  error?: {
    phase: string;
    message: string;
    code?: string;
  };
  resources: {
    projectId?: string;
    environmentId?: string;
    applicationId?: string;
    domainId?: string;
  };
  timestamps: {
    started: string;
    completed?: string;
  };
}

export interface DeploymentResult {
  success: boolean;
  state: DeploymentState;
  logs: string[];
}

export type ProgressCallback = (state: DeploymentState) => void;

export class ProductionDeployer {
  private client: DokployProductionClient;
  private progressCallback?: ProgressCallback;
  private t: ReturnType<typeof createTranslator>;

  constructor(client: DokployProductionClient, progressCallback?: ProgressCallback) {
    this.client = client;
    this.progressCallback = progressCallback;
    this.t = createTranslator('en');
  }

  private notifyProgress(state: DeploymentState): void {
    if (this.progressCallback) {
      this.progressCallback({ ...state });
    }
  }

  /**
   * Deploy a complete AI stack with full production safeguards
   */
  async deploy(config: DeploymentConfig): Promise<DeploymentResult> {
    this.t = createTranslator((config.lang || 'en') as BackendLanguage);
    
    const state: DeploymentState = {
      id: `dep_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
      stackName: config.stackName,
      phase: 'initializing',
      status: 'in_progress',
      progress: 0,
      message: this.t('initializing'),
      resources: {},
      timestamps: {
        started: new Date().toISOString(),
      },
    };

    this.notifyProgress(state);

    try {
      // Phase 1: Project Creation (Idempotent)
      await this.createOrFindProject(state, config);
      this.notifyProgress(state);

      // Phase 2: Get Environment ID
      await this.getEnvironment(state);
      this.notifyProgress(state);

      // Phase 3: Application Creation (Idempotent)
      await this.createOrFindApplication(state, config);
      this.notifyProgress(state);

      // Phase 4: Configure Application
      await this.configureApplication(state, config);
      this.notifyProgress(state);

      // Phase 5: Domain Creation (Idempotent)
      await this.createOrFindDomain(state, config);
      this.notifyProgress(state);

      // Phase 6: Deploy Application
      await this.deployApplication(state);
      this.notifyProgress(state);

      // Phase 7: Health Verification
      await this.verifyHealth(state, config);
      this.notifyProgress(state);

      // Success
      state.phase = 'completed';
      state.status = 'success';
      state.progress = 100;
      state.message = 'Deployment completed successfully';
      state.timestamps.completed = new Date().toISOString();

      this.notifyProgress(state);

      return {
        success: true,
        state,
        logs: this.client.getLogs().map(l => JSON.stringify(l)),
      };

    } catch (error) {
      // Failure - Initiate Rollback
      state.status = 'failure';
      state.error = {
        phase: state.phase,
        message: error instanceof Error ? error.message : String(error),
        code: 'DEPLOYMENT_FAILED',
      };

      this.notifyProgress(state);

      console.error(`Deployment failed at phase ${state.phase}:`, error);

      // Attempt rollback
      await this.rollback(state);
      this.notifyProgress(state);

      state.timestamps.completed = new Date().toISOString();

      this.notifyProgress(state);

      return {
        success: false,
        state,
        logs: this.client.getLogs().map(l => JSON.stringify(l)),
      };
    }
  }

  private async createOrFindProject(
    state: DeploymentState,
    config: DeploymentConfig
  ): Promise<void> {
    state.phase = 'creating_project';
    state.progress = 10;
    state.message = 'Using shared project for deployment';

    // Use shared project and environment IDs from config or env vars
    const sharedProjectId = config.sharedProjectId || process.env.SHARED_PROJECT_ID;
    const sharedEnvironmentId = config.sharedEnvironmentId || process.env.SHARED_ENVIRONMENT_ID;

    if (sharedProjectId && sharedEnvironmentId) {
      console.log(`Using shared project: ${sharedProjectId}, environment: ${sharedEnvironmentId}`);
      state.resources.projectId = sharedProjectId;
      state.resources.environmentId = sharedEnvironmentId;
      state.message = 'Using shared project';
      return;
    }

    // Fallback to legacy behavior if shared IDs not configured
    const projectName = `ai-stack-${config.stackName}`;
    const existingProject = await this.client.findProjectByName(projectName);

    if (existingProject) {
      console.log(`Project ${projectName} already exists, reusing...`);
      state.resources.projectId = existingProject.project.projectId;
      if (existingProject.environmentId) {
        state.resources.environmentId = existingProject.environmentId;
      }
      state.message = 'Found existing project';
      return;
    }

    const response = await this.client.createProject(
      projectName,
      `AI Stack for ${config.stackName}`
    );

    console.log('Project and environment created:', JSON.stringify(response, null, 2));

    if (!response.project.projectId) {
      throw new Error(`Project creation succeeded but projectId is missing. Response: ${JSON.stringify(response)}`);
    }

    state.resources.projectId = response.project.projectId;
    state.resources.environmentId = response.environment.environmentId;
    state.message = 'Project and environment created';
  }

  private async getEnvironment(state: DeploymentState): Promise<void> {
    state.phase = 'getting_environment';
    state.progress = 25;
    state.message = this.t('gettingEnvironment');

    // Skip if we already have environment ID from project creation
    if (state.resources.environmentId) {
      console.log('Environment ID already available from project creation');
      state.message = this.t('environmentAvailable');
      return;
    }

    if (!state.resources.projectId) {
      throw new Error('Project ID not available');
    }

    const environment = await this.client.getDefaultEnvironment(state.resources.projectId);
    state.resources.environmentId = environment.environmentId;
    state.message = this.t('environmentRetrieved');
  }

  private async createOrFindApplication(
    state: DeploymentState,
    config: DeploymentConfig
  ): Promise<void> {
    state.phase = 'creating_application';
    state.progress = 40;
    state.message = this.t('creatingApplication');

    if (!state.resources.environmentId) {
      throw new Error('Environment ID not available');
    }

    const appName = `opencode-${config.stackName}`;

    // Note: Idempotency for applications requires listing all applications
    // in the environment and checking by name. For now, we rely on API
    // to handle duplicates or we can add this later.

    const application = await this.client.createApplication(
      appName,
      state.resources.environmentId
    );

    state.resources.applicationId = application.applicationId;
    state.message = 'Application created';
  }

  private async configureApplication(
    state: DeploymentState,
    config: DeploymentConfig
  ): Promise<void> {
    state.phase = 'configuring_application';
    state.progress = 50;
    state.message = this.t('configuringApplication');

    if (!state.resources.applicationId) {
      throw new Error('Application ID not available');
    }

    await this.client.updateApplication(state.resources.applicationId, {
      dockerImage: config.dockerImage,
      sourceType: 'docker',
      registryId: config.registryId,
    });

    state.progress = 52;
    state.message = 'Setting environment variables for logging';

    const envVars = [
      `STACK_NAME=${config.stackName}`,
      `USAGE_LOGGING_ENABLED=true`,
      `LOG_INGEST_URL=${process.env.LOG_INGEST_URL || 'http://10.100.0.20:3102/ingest'}`,
      `METRICS_PORT=9090`,
      // TUI Support: Terminal environment for proper TUI rendering in web browser
      `TERM=xterm-256color`,
      `COLORTERM=truecolor`,
      `LANG=en_US.UTF-8`,
      `LC_ALL=en_US.UTF-8`,
    ].join('\n');

    await this.client.setApplicationEnv(state.resources.applicationId, envVars);

    state.progress = 55;
    state.message = 'Creating persistent storage';

    const volumeName = `portal-ai-workspace-${config.stackName}`;
    try {
      await this.client.createMount(
        state.resources.applicationId,
        volumeName,
        '/workspace'
      );
      console.log(`Created persistent volume: ${volumeName}`);
    } catch (error) {
      console.warn(`Volume creation failed (may already exist): ${error}`);
    }

    state.message = 'Application configured with storage';
  }

  private async createOrFindDomain(
    state: DeploymentState,
    config: DeploymentConfig
  ): Promise<void> {
    state.phase = 'creating_domain';
    state.progress = 70;
    state.message = this.t('creatingDomain');

    if (!state.resources.applicationId) {
      throw new Error('Application ID not available');
    }

    const host = `${config.stackName}.${config.domainSuffix}`;
    const port = config.port || 8080;

    // Note: Idempotency for domains would require listing existing domains
    // for the application. For now, API should handle duplicates.

    const domain = await this.client.createDomain(
      host,
      state.resources.applicationId,
      true,
      port
    );

    state.resources.domainId = domain.domainId;
    state.url = `https://${host}`;
    state.message = 'Domain created';
  }

  private async deployApplication(state: DeploymentState): Promise<void> {
    state.phase = 'deploying';
    state.progress = 85;
    state.message = this.t('deployingApplication');

    if (!state.resources.applicationId) {
      throw new Error('Application ID not available');
    }

    await this.client.deployApplication(state.resources.applicationId);
    state.message = 'Deployment triggered';
  }

  private async verifyHealth(
    state: DeploymentState,
    config: DeploymentConfig
  ): Promise<void> {
    state.phase = 'verifying_health';
    state.progress = 95;
    state.message = this.t('verifyingHealth');

    if (!state.resources.applicationId) {
      throw new Error('Application ID not available');
    }

    const timeout = config.healthCheckTimeout || 60000;
    const interval = config.healthCheckInterval || 3000;
    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      try {
        const app = await this.client.getApplication(state.resources.applicationId);
        const appStatus = app.applicationStatus;
        console.log(`Application status: ${appStatus}`);
        
        if (appStatus === 'done') {
          state.message = this.t('waitingForSSL');
          state.progress = 98;
          this.notifyProgress(state);
          
          await this.sleep(15000);
          
          state.message = this.t('deploymentSuccess');
          return;
        }
        
        if (appStatus === 'error') {
          throw new Error('Application deployment failed in Dokploy');
        }
      } catch (error) {
        console.log(`Status check failed: ${error}`);
      }

      const elapsed = Math.round((Date.now() - startTime) / 1000);
      state.message = `${this.t('waitingForStart')} (${elapsed}s)...`;
      this.notifyProgress(state);

      await this.sleep(interval);
    }

    throw new Error('Health check timeout - application did not become ready');
  }

  private async rollback(state: DeploymentState): Promise<void> {
    console.log('Initiating rollback...');
    state.phase = 'rolling_back';
    state.message = 'Rolling back deployment';

    const isSharedProject = !!(process.env.SHARED_PROJECT_ID || process.env.SHARED_ENVIRONMENT_ID);

    try {
      if (state.resources.domainId) {
        console.log(`Rolling back: deleting domain ${state.resources.domainId}`);
        try {
          await this.client.deleteDomain(state.resources.domainId);
        } catch (error) {
          console.error('Failed to delete domain during rollback:', error);
        }
      }

      if (state.resources.applicationId) {
        console.log(`Rolling back: deleting application ${state.resources.applicationId}`);
        try {
          await this.client.deleteApplication(state.resources.applicationId);
        } catch (error) {
          console.error('Failed to delete application during rollback:', error);
        }
      }

      if (state.resources.projectId && !isSharedProject) {
        console.log(`Rolling back: deleting project ${state.resources.projectId}`);
        try {
          await this.client.deleteProject(state.resources.projectId);
        } catch (error) {
          console.error('Failed to delete project during rollback:', error);
        }
      }

      state.message = 'Rollback completed';
    } catch (error) {
      console.error('Rollback failed:', error);
      state.message = 'Rollback failed - manual cleanup required';
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}
