import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireAdminIdentity } from '../helpers/guards.ts';
import { getErrorMessage } from '../../utils/error.ts';

type IAppStoreUpgradeOperation = interfaces.requests.IAppStoreUpgradeOperation;
type TAppStoreUpgradeStep = interfaces.requests.TAppStoreUpgradeStep;

export class AppStoreHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();
  private upgradeOperations = new Map<string, IAppStoreUpgradeOperation>();

  constructor(private opsServerRef: OpsServer) {
    this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
    this.registerHandlers();
  }

  private getUpgradeOperations(): IAppStoreUpgradeOperation[] {
    return Array.from(this.upgradeOperations.values())
      .sort((a, b) => b.startedAt - a.startedAt)
      .slice(0, 25);
  }

  public getUpgradeOperationsForService(serviceNameArg: string): IAppStoreUpgradeOperation[] {
    return this.getUpgradeOperations().filter((operationArg) => operationArg.serviceName === serviceNameArg);
  }

  public async startHostedAppUpgrade(serviceNameArg: string, targetVersionArg: string): Promise<IAppStoreUpgradeOperation> {
    const operation = await this.createUpgradeOperation(serviceNameArg, targetVersionArg);
    void this.performUpgrade(operation.id).catch(() => {});
    return operation;
  }

  private getRunningUpgrade(serviceNameArg: string): IAppStoreUpgradeOperation | null {
    for (const operation of this.upgradeOperations.values()) {
      if (operation.serviceName === serviceNameArg && operation.status === 'running') {
        return operation;
      }
    }
    return null;
  }

  private async createUpgradeOperation(
    serviceNameArg: string,
    targetVersionArg: string,
  ): Promise<IAppStoreUpgradeOperation> {
    const existingRunning = this.getRunningUpgrade(serviceNameArg);
    if (existingRunning) {
      throw new plugins.typedrequest.TypedResponseError(
        `An upgrade is already running for ${serviceNameArg}`,
      );
    }

    const existingService = this.opsServerRef.oneboxRef.database.getServiceByName(serviceNameArg);
    if (!existingService) {
      throw new plugins.typedrequest.TypedResponseError(`Service not found: ${serviceNameArg}`);
    }
    if (!existingService.appTemplateId) {
      throw new plugins.typedrequest.TypedResponseError('Service was not deployed from an app template');
    }
    if (!existingService.appTemplateVersion) {
      throw new plugins.typedrequest.TypedResponseError('Service has no tracked template version');
    }

    const now = Date.now();
    const operation: IAppStoreUpgradeOperation = {
      id: crypto.randomUUID(),
      serviceName: existingService.name,
      appTemplateId: existingService.appTemplateId,
      fromVersion: existingService.appTemplateVersion,
      targetVersion: targetVersionArg,
      status: 'running',
      step: 'queued',
      progressLines: [`Queued upgrade ${existingService.appTemplateVersion} -> ${targetVersionArg}`],
      warnings: [],
      startedAt: now,
      updatedAt: now,
    };

    this.upgradeOperations.set(operation.id, operation);
    await this.pushUpgradeProgress(operation);
    return operation;
  }

  private async updateUpgradeOperation(
    operationIdArg: string,
    stepArg: TAppStoreUpgradeStep,
    messageArg: string,
    updatesArg: Partial<IAppStoreUpgradeOperation> = {},
  ): Promise<IAppStoreUpgradeOperation> {
    const existing = this.upgradeOperations.get(operationIdArg);
    if (!existing) {
      throw new Error(`Upgrade operation not found: ${operationIdArg}`);
    }

    const nextOperation: IAppStoreUpgradeOperation = {
      ...existing,
      ...updatesArg,
      step: stepArg,
      updatedAt: Date.now(),
      progressLines: [...existing.progressLines, messageArg].slice(-200),
    };
    this.upgradeOperations.set(operationIdArg, nextOperation);
    await this.pushUpgradeProgress(nextOperation);
    return nextOperation;
  }

  private async pushUpgradeProgress(operationArg: IAppStoreUpgradeOperation): Promise<void> {
    await this.opsServerRef.pushDashboardEvent('pushAppStoreUpgradeProgress', {
      operation: operationArg,
    });
  }

  private async performUpgrade(operationIdArg: string): Promise<interfaces.data.IService> {
    let operation = this.upgradeOperations.get(operationIdArg);
    if (!operation) {
      throw new Error(`Upgrade operation not found: ${operationIdArg}`);
    }

    try {
      operation = await this.updateUpgradeOperation(
        operation.id,
        'validating',
        `Validating ${operation.serviceName} for App Store upgrade`,
      );

      const existingService = this.opsServerRef.oneboxRef.database.getServiceByName(operation.serviceName);
      if (!existingService) {
        throw new Error(`Service not found: ${operation.serviceName}`);
      }
      if (!existingService.appTemplateId || !existingService.appTemplateVersion) {
        throw new Error('Service is missing App Store template metadata');
      }

      logger.info(
        `Upgrading service '${operation.serviceName}' from v${operation.fromVersion} to v${operation.targetVersion}`,
      );

      await this.updateUpgradeOperation(
        operation.id,
        'migration',
        `Resolving migration for ${operation.appTemplateId} ${operation.fromVersion} -> ${operation.targetVersion}`,
      );

      const migrationResult = await this.opsServerRef.oneboxRef.appStore.executeMigration(
        existingService,
        operation.fromVersion,
        operation.targetVersion,
      );

      if (!migrationResult.success) {
        throw new Error(`Migration failed: ${migrationResult.warnings.join('; ')}`);
      }

      if (migrationResult.warnings.length > 0) {
        operation = await this.updateUpgradeOperation(
          operation.id,
          'migration',
          `Migration completed with ${migrationResult.warnings.length} warning(s)`,
          { warnings: migrationResult.warnings },
        );
      }

      await this.updateUpgradeOperation(
        operation.id,
        'applying',
        `Applying upgrade to ${operation.serviceName}`,
      );

      const updatedService = await this.opsServerRef.oneboxRef.appStore.applyUpgrade(
        operation.serviceName,
        migrationResult,
        operation.targetVersion,
        {
          onProgress: async (progressArg) => {
            await this.updateUpgradeOperation(
              operation!.id,
              progressArg.step as TAppStoreUpgradeStep,
              progressArg.message,
            );
          },
        },
      );

      await this.updateUpgradeOperation(
        operation.id,
        'complete',
        `Upgrade completed for ${operation.serviceName}`,
        {
          status: 'success',
          completedAt: Date.now(),
          service: updatedService,
          warnings: migrationResult.warnings,
        },
      );

      return updatedService;
    } catch (error) {
      await this.updateUpgradeOperation(
        operation.id,
        'failed',
        `Upgrade failed: ${getErrorMessage(error)}`,
        {
          status: 'failed',
          completedAt: Date.now(),
          error: getErrorMessage(error),
        },
      );
      throw error;
    }
  }

  private registerHandlers(): void {
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppStoreTemplates>(
        'getAppStoreTemplates',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const apps = await this.opsServerRef.oneboxRef.appStore.getApps();
          return { apps };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppStoreConfig>(
        'getAppStoreConfig',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const config = await this.opsServerRef.oneboxRef.appStore.getAppVersionConfig(
            dataArg.appId,
            dataArg.version,
          );
          const appMeta = await this.opsServerRef.oneboxRef.appStore.getAppMeta(dataArg.appId);
          return { config, appMeta };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_InstallAppStoreApp>(
        'installAppStoreApp',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.appStore.installApp(dataArg.install);
          return { service };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetUpgradeableAppStoreServices>(
        'getUpgradeableAppStoreServices',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const services = await this.opsServerRef.oneboxRef.appStore.getUpgradeableAppStoreServices();
          return { services };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpgradeAppStoreService>(
        'upgradeAppStoreService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const operation = await this.createUpgradeOperation(dataArg.serviceName, dataArg.targetVersion);
          const updatedService = await this.performUpgrade(operation.id);
          const completedOperation = this.upgradeOperations.get(operation.id)!;

          return {
            service: updatedService,
            warnings: completedOperation.warnings,
          };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartAppStoreServiceUpgrade>(
        'startAppStoreServiceUpgrade',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const operation = await this.createUpgradeOperation(dataArg.serviceName, dataArg.targetVersion);
          void this.performUpgrade(operation.id).catch(() => {});
          return { operation };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAppStoreUpgradeOperations>(
        'getAppStoreUpgradeOperations',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          return { operations: this.getUpgradeOperations() };
        },
      ),
    );
  }
}
