import type { FastifyBaseLogger } from 'fastify';
import { KubernetesClient, type DeploymentSpec } from './client.js';
import type { License, LicenseTier } from '../types/user.js';
import type { UserService } from '../db/user-service.js';

export interface ContainerManagerConfig {
  k8sClient: KubernetesClient;
  userService: UserService;
  sandboxImage: string;
  sidecarImage: string;
  storageClass: string;
  imagePullPolicy?: string;
  namespace: string;
  serviceNamespace: string;
  logger: FastifyBaseLogger;
}

export interface ContainerStatus {
  exists: boolean;
  ready: boolean;
  mcpEndpoint: string;
}

export interface EnsureContainerResult {
  mcpEndpoint: string;
  wasCreated: boolean;
  isSpinningUp: boolean;
}

/**
 * Container manager orchestrates sandbox container lifecycle
 */
export class ContainerManager {
  private config: ContainerManagerConfig;

  constructor(config: ContainerManagerConfig) {
    this.config = config;
  }

  /**
   * Ensure user's container is running and ready
   * Returns the MCP endpoint URL
   * If waitForReady is false, will return immediately after creating the deployment
   */
  async ensureContainerRunning(
    userId: string,
    license: License,
    waitForReady: boolean = true
  ): Promise<EnsureContainerResult> {
    const deploymentName = KubernetesClient.getDeploymentName(userId);
    const mcpEndpoint = KubernetesClient.getMcpEndpoint(userId, this.config.namespace);

    this.config.logger.info(
      { userId, licenseType: license.licenseType, deploymentName },
      'Ensuring container is running'
    );

    // Check if deployment already exists
    const exists = await this.config.k8sClient.deploymentExists(deploymentName);

    if (exists) {
      this.config.logger.info({ userId, deploymentName }, 'Container deployment already exists');

      if (waitForReady) {
        // Wait for it to be ready (in case it's starting up)
        const ready = await this.config.k8sClient.waitForDeploymentReady(deploymentName, 30000);

        if (!ready) {
          this.config.logger.warn(
            { userId, deploymentName },
            'Existing deployment not ready within timeout'
          );
          // Continue anyway - might be an image pull or other transient issue
        }
      }

      return { mcpEndpoint, wasCreated: false, isSpinningUp: false };
    }

    // Create new deployment
    this.config.logger.info({ userId, licenseType: license.licenseType }, 'Creating new container');

    const spec: DeploymentSpec = {
      userId,
      licenseType: license.licenseType,
      k8sResources: license.k8sResources,
      sandboxImage: this.config.sandboxImage,
      sidecarImage: this.config.sidecarImage,
      storageClass: this.config.storageClass,
      imagePullPolicy: this.config.imagePullPolicy,
      serviceNamespace: this.config.serviceNamespace,
    };

    await this.config.k8sClient.createAgentDeployment(spec);

    // If not waiting for ready, return immediately with spinning up status
    if (!waitForReady) {
      this.config.logger.info({ userId, deploymentName }, 'Container created, spinning up...');
      return { mcpEndpoint, wasCreated: true, isSpinningUp: true };
    }

    // Wait for deployment to be ready
    const ready = await this.config.k8sClient.waitForDeploymentReady(deploymentName, 120000);

    if (!ready) {
      throw new Error(
        `Container deployment failed to become ready within timeout: ${deploymentName}`
      );
    }

    this.config.logger.info({ userId, mcpEndpoint }, 'Container is ready');

    return { mcpEndpoint, wasCreated: true, isSpinningUp: false };
  }

  /**
   * Wait for a deployment to become ready
   * Used for background polling after initial creation
   */
  async waitForContainerReady(userId: string, timeoutMs: number = 120000): Promise<boolean> {
    const deploymentName = KubernetesClient.getDeploymentName(userId);
    return await this.config.k8sClient.waitForDeploymentReady(deploymentName, timeoutMs);
  }

  /**
   * Check container status without creating it
   */
  async getContainerStatus(userId: string): Promise<ContainerStatus> {
    const deploymentName = KubernetesClient.getDeploymentName(userId);
    const mcpEndpoint = KubernetesClient.getMcpEndpoint(userId, this.config.namespace);

    const exists = await this.config.k8sClient.deploymentExists(deploymentName);

    if (!exists) {
      return { exists: false, ready: false, mcpEndpoint };
    }

    // Check if ready (with short timeout)
    const ready = await this.config.k8sClient.waitForDeploymentReady(deploymentName, 5000);

    return { exists: true, ready, mcpEndpoint };
  }

  /**
   * Apply a canonical license tier to a user: updates DB and deletes the deployment
   * so it is recreated with the new resource limits on next connect.
   */
  async applyLicenseTier(userId: string, tier: LicenseTier): Promise<License> {
    const license = await this.config.userService.setUserLicenseTier(userId, tier);
    await this.config.k8sClient.deleteDeploymentOnly(userId);
    this.config.logger.info({ userId, tier }, 'License tier applied; deployment will recreate on next connect');
    return license;
  }

  /**
   * Delete container (for cleanup/testing)
   */
  async deleteContainer(userId: string): Promise<void> {
    await this.config.k8sClient.deleteAgentDeployment(userId);
  }
}
