// Deployment Orchestrator Service
// Orchestrates one-click deployment from portal

import { iruProvisioningService } from '../provisioning/iru-provisioning.service';
import { proxmoxVEIntegration } from '@/infrastructure/proxmox/proxmox-ve-integration.service';
import prisma from '@/shared/database/prisma';
import { DbisError, ErrorCode } from '@/shared/types';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '@/infrastructure/monitoring/logger';
import { notificationService } from '@/core/iru/notifications/notification.service';

import { DeploymentConfig, DeploymentMetadata, ProvisioningResult } from '../types/common.types';

export interface DeploymentRequest {
  subscriptionId: string;
  deploymentConfig?: DeploymentConfig;
}

export interface DeploymentStatus {
  deploymentId: string;
  subscriptionId: string;
  status: string;
  progress: number;
  stages: Array<{
    name: string;
    status: string;
    startedAt?: Date;
    completedAt?: Date;
    error?: string;
  }>;
  containers: Array<{
    name: string;
    status: string;
    ip?: string;
  }>;
  estimatedCompletion?: Date;
}

export class DeploymentOrchestrator {
  /**
   * Initiate deployment
   */
  async initiateDeployment(request: DeploymentRequest): Promise<DeploymentStatus> {
    const subscription = await prisma.iruSubscription.findUnique({
      where: { subscriptionId: request.subscriptionId },
      include: {
        offering: true,
        inquiry: { select: { contactEmail: true, organizationName: true } },
      },
    });

    if (!subscription) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Subscription ${request.subscriptionId} not found`);
    }

    if (subscription.subscriptionStatus !== 'active') {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        `Subscription ${request.subscriptionId} is not active`
      );
    }

    const deploymentId = `DEP-${uuidv4().substring(0, 8).toUpperCase()}`;

    // Check if this is a Vault offering (no containers needed)
    const isVaultOffering = subscription.offering.offeringId === 'VAULT-VIRTUAL-VAULT';
    
    let provisioning;
    if (isVaultOffering) {
      // For Vault, we don't need container provisioning
      // Create minimal provisioning result
      provisioning = {
        resources: {
          containers: [], // No containers for Vault
          network: {
            vlan: 0,
            subnet: '192.168.11.0/24',
            gateway: '192.168.11.1',
          },
          storage: {
            total: 0,
            pools: [],
          },
        },
        configuration: {
          containers: [],
        },
        estimatedCompletion: new Date(Date.now() + 30 * 60 * 1000), // 30 minutes
      };
    } else {
      // Step 1: Provision resources
      provisioning = await iruProvisioningService.provision({
        subscriptionId: request.subscriptionId,
        deploymentConfig: request.deploymentConfig,
      });
    }

    // Create deployment record in database
    await prisma.iruDeployment.create({
      data: {
        id: uuidv4(),
        deploymentId,
        subscriptionId: subscription.id,
        status: 'provisioning',
        progress: 10,
        stages: [
          {
            name: 'Resource Allocation',
            status: 'completed',
            completedAt: new Date(),
          },
          {
            name: 'Container Deployment',
            status: 'in_progress',
            startedAt: new Date(),
          },
        ],
        containers: provisioning.resources.containers.map((c) => ({
          name: c.name,
          status: 'pending',
        })),
        estimatedCompletion: provisioning.estimatedCompletion,
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    });

    // Step 2: Deploy containers (async)
    this.deployContainers(deploymentId, provisioning, subscription.subscriptionId, request.deploymentConfig).catch(async (error) => {
      logger.error('Deployment failed', {
        deploymentId,
        subscriptionId: subscription.subscriptionId,
        error: error instanceof Error ? error.message : 'Unknown error',
        stack: error instanceof Error ? error.stack : undefined,
      });

      // Update deployment status to failed
      await this.updateDeploymentStatus(deploymentId, 'failed', {
        error: error instanceof Error ? error.message : 'Unknown error',
      });

      // Send notification
      try {
        // Get participant email from inquiry
        const participantEmail = subscription.inquiry?.contactEmail || subscription.participantId;
        
        await notificationService.sendNotification({
          recipient: participantEmail,
          recipientType: 'email',
          template: 'deployment-failed',
          variables: {
            deploymentId,
            subscriptionId: subscription.subscriptionId,
            error: error instanceof Error ? error.message : 'Unknown error',
          },
        });
      } catch (notifError) {
        logger.error('Failed to send deployment failure notification', {
          deploymentId,
          error: notifError instanceof Error ? notifError.message : 'Unknown error',
        });
      }
    });

    return {
      deploymentId,
      subscriptionId: request.subscriptionId,
      status: 'provisioning',
      progress: 10,
      stages: [
        {
          name: 'Resource Allocation',
          status: 'completed',
          completedAt: new Date(),
        },
        {
          name: 'Container Deployment',
          status: 'in_progress',
          startedAt: new Date(),
        },
        {
          name: 'Network Configuration',
          status: 'pending',
        },
        {
          name: 'Security Hardening',
          status: 'pending',
        },
        {
          name: 'Service Activation',
          status: 'pending',
        },
      ],
      containers: provisioning.resources.containers.map((c) => ({
        name: c.name,
        status: 'pending',
      })),
      estimatedCompletion: provisioning.estimatedCompletion,
    };
  }

  /**
   * Update deployment status
   */
  private async updateDeploymentStatus(
    deploymentId: string,
    status: string,
    metadata?: DeploymentMetadata
  ): Promise<void> {
    try {
      await prisma.iruDeployment.update({
        where: { deploymentId },
        data: {
          status,
          progress: status === 'deployed' ? 100 : status === 'failed' ? 0 : undefined,
          metadata: metadata ? { ...metadata } : undefined,
          updatedAt: new Date(),
        },
      });
    } catch (error) {
      logger.error('Failed to update deployment status', {
        deploymentId,
        status,
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  }

  /**
   * Deploy containers (async)
   */
  private async deployContainers(
    deploymentId: string,
    provisioning: ProvisioningResult,
    subscriptionId: string,
    deploymentConfig?: DeploymentConfig
  ): Promise<void> {
    try {
      // Get subscription to check offering type
      const subscription = await prisma.iruSubscription.findUnique({
        where: { subscriptionId },
        include: {
          offering: true,
          inquiry: true,
        },
      });

      if (!subscription) {
        throw new Error(`Subscription ${subscriptionId} not found`);
      }

      const isVaultOffering = subscription.offering.offeringId === 'VAULT-VIRTUAL-VAULT';

      // Convert provisioning resources to Proxmox container specs (skip for Vault)
      const containerSpecs = isVaultOffering
        ? []
        : provisioning.resources.containers.map((container, index: number) => ({
            vmid: 20000 + index, // Generate VMID
            hostname: container.name,
            cores: container.resources.cpu,
            memory: container.resources.memory,
            disk: container.resources.disk,
            network: {
              ip: container.network.ip,
              gateway: provisioning.resources.network.gateway,
              vlan: provisioning.resources.network.vlan,
            },
            template: this.getTemplateForContainerType(container.type),
            config: provisioning.configuration.containers.find((c) => c.name === container.name)?.config || {},
          }));

      // Deploy containers via Proxmox VE (skip for Vault)
      let containerDeploymentResult: any = { containers: [] };
      if (containerSpecs.length > 0) {
        const result = await proxmoxVEIntegration.deployIRUContainers(containerSpecs);

        if (!result.success) {
          throw new Error(`Deployment failed: ${result.errors?.join(', ')}`);
        }
        containerDeploymentResult = result;
      }

      // Configure services (Besu, FireFly, Vault, AS4 Settlement, etc.)
      const isAs4SettlementOffering = subscription.offering.offeringId === 'AS4-SETTLEMENT-MASTER';
      
      if (isVaultOffering) {
        // Provision virtual vault
        const { vaultProvisioningService } = await import('../provisioning/vault-provisioning.service');
        const { vaultServiceConfigService } = await import('./vault-service-config.service');
        
        const vaultResult = await vaultProvisioningService.provisionVirtualVault({
          subscriptionId: subscription.subscriptionId,
          organizationName: subscription.inquiry?.organizationName || 'Unknown',
          vaultName: deploymentConfig?.vaultName || `vault-${subscription.subscriptionId}`,
          capacityTier: subscription.capacityTier,
          deploymentConfig: deploymentConfig?.vaultConfig,
        });

        // Configure Vault service
        const vaultConfigResult = await vaultServiceConfigService.configureVaultService({
          vaultId: vaultResult.vaultId,
          vaultPath: vaultResult.vaultPath,
          roleId: vaultResult.roleId,
          secretId: vaultResult.secretId,
          apiEndpoint: vaultResult.apiEndpoint,
          organizationId: vaultResult.metadata.organizationId,
          subscriptionId: subscription.subscriptionId,
        });

        if (!vaultConfigResult.success) {
          throw new Error(`Vault service configuration failed: ${vaultConfigResult.error}`);
        }

        // Store vault credentials in deployment metadata
        const deployment = await prisma.iruDeployment.findUnique({
          where: { deploymentId },
        });
        
        await prisma.iruDeployment.update({
          where: { deploymentId },
          data: {
            metadata: {
              ...((deployment?.metadata as Record<string, unknown>) || {}),
              vault: {
                vaultId: vaultResult.vaultId,
                vaultPath: vaultResult.vaultPath,
                apiEndpoint: vaultResult.apiEndpoint,
                roleId: vaultResult.roleId,
                secretId: vaultResult.secretId, // Note: In production, encrypt this
              },
            },
          },
        });
      } else if (isAs4SettlementOffering) {
        // Provision AS4 Settlement service
        const { as4SettlementProvisioningService } = await import('../provisioning/as4-settlement-provisioning.service');
        const { as4SettlementConfigService } = await import('./as4-settlement-config.service');
        
        const as4Result = await as4SettlementProvisioningService.provision({
          subscriptionId: subscription.subscriptionId,
          organizationName: subscription.inquiry?.organizationName || 'Unknown',
          capacityTier: subscription.capacityTier || 3,
          deploymentConfig: deploymentConfig?.as4Config,
        });

        if (!as4Result.success) {
          throw new Error(`AS4 Settlement provisioning failed: ${as4Result.error}`);
        }

        // Configure AS4 Settlement service
        const as4ConfigResult = await as4SettlementConfigService.configureService({
          subscriptionId: subscription.subscriptionId,
          organizationName: subscription.inquiry?.organizationName || 'Unknown',
          capacityTier: subscription.capacityTier || 3,
          config: deploymentConfig?.as4Config,
        });

        if (!as4ConfigResult.success) {
          throw new Error(`AS4 Settlement configuration failed: ${as4ConfigResult.error}`);
        }

        // Store AS4 credentials in deployment metadata
        const deployment = await prisma.iruDeployment.findUnique({
          where: { deploymentId },
        });
        
        await prisma.iruDeployment.update({
          where: { deploymentId },
          data: {
            metadata: {
              ...((deployment?.metadata as Record<string, unknown>) || {}),
              as4Settlement: {
                memberId: as4Result.memberId,
                as4EndpointUrl: as4Result.as4EndpointUrl,
              },
            },
          },
        });
      } else {
        // Configure other services (Besu, FireFly, etc.)
        const { serviceConfigService } = await import('./service-config.service');
        const serviceConfigs = provisioning.resources.containers.map((container, index: number) => ({
          serviceType: container.type === 'besu-sentry' ? 'besu' : container.type === 'firefly-core' ? 'firefly' : 'monitoring',
          containerId: container.name,
          containerIP: container.network.ip,
          config: provisioning.configuration.containers.find((c) => c.name === container.name)?.config || {},
        }));

        const configResults = await serviceConfigService.configureServices(serviceConfigs);
        const configFailed = configResults.some((r) => !r.success);
        if (configFailed) {
          logger.warn('Some services failed to configure', {
            deploymentId,
            failed: configResults.filter((r) => !r.success).map((r) => r.serviceType),
          });
        }
      }

      // Apply security hardening (skip for Vault - no containers)
      if (!isVaultOffering && provisioning.resources.containers.length > 0) {
        const { securityHardeningService } = await import('./security-hardening.service');
        const hardeningConfigs = provisioning.resources.containers.map((container) => ({
          containerId: container.name,
          containerIP: container.network.ip,
          containerType: container.type === 'besu-sentry' ? 'besu' : container.type === 'firefly-core' ? 'firefly' : container.type === 'firefly-db' ? 'database' : 'monitoring',
          hardeningLevel: 'standard' as const,
        }));

        for (const hardeningConfig of hardeningConfigs) {
          await securityHardeningService.applyHardening(hardeningConfig);
        }
      }

      // Verify health
      if (isVaultOffering) {
        // For Vault, verify virtual vault is accessible
        const deployment = await prisma.iruDeployment.findUnique({
          where: { deploymentId },
        });
        const vaultMetadata = (deployment?.metadata as any)?.vault;
        if (vaultMetadata) {
          const { vaultServiceConfigService } = await import('./vault-service-config.service');
          const healthCheck = await vaultServiceConfigService.configureVaultService({
            vaultId: vaultMetadata.vaultId,
            vaultPath: vaultMetadata.vaultPath,
            roleId: vaultMetadata.roleId,
            secretId: vaultMetadata.secretId,
            apiEndpoint: vaultMetadata.apiEndpoint,
            organizationId: vaultMetadata.organizationId || '',
            subscriptionId: subscription.subscriptionId,
          });
          if (!healthCheck.success) {
            logger.warn('Vault virtual vault health check failed', {
              deploymentId,
              error: healthCheck.error,
            });
          }
        }
      } else if (provisioning.resources.containers.length > 0) {
        const { healthVerificationService } = await import('./health-verification.service');
        const healthChecks = provisioning.resources.containers.map((container) => ({
          serviceName: container.name,
          serviceType: container.type === 'besu-sentry' ? 'besu' : container.type === 'firefly-core' ? 'firefly' : container.type === 'firefly-db' ? 'database' : 'monitoring',
          endpoint: `http://${container.network.ip}`,
          healthCheckPath: container.type === 'besu-sentry' ? undefined : container.type === 'firefly-core' ? '/api/v1/status' : '/health',
        }));

        const healthResults = await healthVerificationService.verifyHealth(healthChecks);
        const unhealthyServices = healthResults.filter((r) => !r.healthy);
        if (unhealthyServices.length > 0) {
          logger.warn('Some services are unhealthy', {
            deploymentId,
            unhealthy: unhealthyServices.map((r) => r.serviceName),
          });
        }
      }

      // Update deployment status to deployed
      await this.updateDeploymentStatus(deploymentId, 'deployed', {
        containers: isVaultOffering ? [] : containerDeploymentResult.containers || [],
        completedAt: new Date(),
      });

      logger.info('Deployment completed successfully', {
        deploymentId,
        subscriptionId,
        containers: isVaultOffering ? 0 : (containerDeploymentResult.containers?.length || 0),
        isVaultOffering,
      });

      // Send success notification
      try {
        // Get participant email from subscription
        const subscriptionData = await prisma.iruSubscription.findUnique({
          where: { subscriptionId },
          include: {
            inquiry: {
              select: {
                contactEmail: true,
              },
            },
          },
        });
        
        const participantEmail = subscriptionData?.inquiry?.contactEmail ?? null;

        if (participantEmail) {
          await notificationService.sendNotification({
            recipient: participantEmail,
            recipientType: 'email',
            template: 'deployment-complete',
            variables: {
              deploymentId,
              subscriptionId,
            },
          });
        } else {
          logger.warn('No participant email found for deployment notification', { deploymentId, subscriptionId });
        }
      } catch (notifError) {
        logger.error('Failed to send deployment success notification', {
          deploymentId,
          error: notifError instanceof Error ? notifError.message : 'Unknown error',
        });
      }
    } catch (error) {
      logger.error('Deployment failed', {
        deploymentId,
        subscriptionId,
        error: error instanceof Error ? error.message : 'Unknown error',
        stack: error instanceof Error ? error.stack : undefined,
      });

      // Update deployment status to failed
      await this.updateDeploymentStatus(deploymentId, 'failed', {
        error: error instanceof Error ? error.message : 'Unknown error',
      });

      throw error;
    }
  }

  /**
   * Get template for container type
   */
  private getTemplateForContainerType(type: string): string {
    const templateMap: Record<string, string> = {
      'besu-sentry': 'ubuntu-22.04',
      'firefly-core': 'ubuntu-22.04',
      'firefly-db': 'ubuntu-22.04',
      'monitoring': 'ubuntu-22.04',
    };
    return templateMap[type] || 'ubuntu-22.04';
  }

  /**
   * Get deployment status
   */
  async getDeploymentStatus(deploymentId: string): Promise<DeploymentStatus> {
    const deployment = await prisma.iruDeployment.findUnique({
      where: { deploymentId },
      include: {
        subscription: {
          select: {
            subscriptionId: true,
          },
        },
      },
    });

    if (!deployment) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Deployment ${deploymentId} not found`);
    }

    return {
      deploymentId: deployment.deploymentId,
      subscriptionId: deployment.subscription.subscriptionId,
      status: deployment.status,
      progress: deployment.progress || 0,
      stages: (deployment.stages as Array<{ name: string; status: string; startedAt?: Date; completedAt?: Date; error?: string }>) || [],
      containers: (deployment.containers as Array<{ name: string; status: string; ip?: string }>) || [],
      estimatedCompletion: deployment.estimatedCompletion || undefined,
    };
  }

  /**
   * Cancel deployment
   */
  async cancelDeployment(deploymentId: string): Promise<void> {
    const { deploymentRollbackService } = await import('./deployment-rollback.service');
    
    await deploymentRollbackService.rollbackDeployment({
      deploymentId,
      reason: 'Deployment cancelled by user',
    });

    logger.info('Deployment cancelled', { deploymentId });
  }
}

export const deploymentOrchestrator = new DeploymentOrchestrator();
