import * as plugins from '../../plugins.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
import { requireAdminIdentity } from '../helpers/guards.js';

type IHostedAppLifecycleRecord = import('../../types.js').IHostedAppLifecycleRecord;
type IService = import('../../types.js').IService;
type IHostedAppLifecycleState = interfaces.data.IHostedAppLifecycleState;
type IHostedAppUpgradeState = interfaces.data.IHostedAppUpgradeState;

export class HostedAppHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();

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

  private async requireHostedAppIdentity(
    identityArg: import('@serve.zone/interfaces').data.IHostedAppRuntimeIdentity,
  ): Promise<{ record: IHostedAppLifecycleRecord; service: IService }> {
    const record = identityArg?.appInstanceId
      ? await this.opsServerRef.oneboxRef.database.getHostedAppLifecycleByInstanceId(identityArg.appInstanceId)
      : null;
    if (!record) {
      throw new plugins.typedrequest.TypedResponseError('Hosted app identity is invalid');
    }
    const serviceSecrets = await this.opsServerRef.oneboxRef.database.serviceSecrets.get(record.serviceId);
    if (serviceSecrets.SERVEZONE_APP_CONTROL_TOKEN !== identityArg?.appControlToken) {
      throw new plugins.typedrequest.TypedResponseError('Hosted app identity is invalid');
    }
    const service = await this.opsServerRef.oneboxRef.database.getServiceByID(record.serviceId);
    if (!service) {
      throw new plugins.typedrequest.TypedResponseError('Hosted app service not found');
    }
    return { record, service };
  }

  private async getUpgradeState(serviceArg: IService): Promise<IHostedAppUpgradeState> {
    const latestOperation = this.opsServerRef.appStoreHandler.getUpgradeOperationsForService(serviceArg.name)[0];
    if (latestOperation) {
      return {
        status: latestOperation.status === 'running' ? 'running' : latestOperation.status,
        appTemplateId: latestOperation.appTemplateId,
        currentVersion: latestOperation.fromVersion,
        targetVersion: latestOperation.targetVersion,
        operationId: latestOperation.id,
        warnings: latestOperation.warnings,
        error: latestOperation.error,
        startedAt: latestOperation.startedAt,
        updatedAt: latestOperation.updatedAt,
        completedAt: latestOperation.completedAt,
      };
    }

    if (!serviceArg.appTemplateId || !serviceArg.appTemplateVersion) {
      return { status: 'unknown' };
    }

    const upgradeableServices = await this.opsServerRef.oneboxRef.appStore.getUpgradeableAppStoreServices();
    const upgradeable = upgradeableServices.find((serviceArg2) => serviceArg2.serviceName === serviceArg.name);
    if (!upgradeable) {
      return {
        status: 'upToDate',
        appTemplateId: serviceArg.appTemplateId,
        currentVersion: serviceArg.appTemplateVersion,
        latestVersion: serviceArg.appTemplateVersion,
      };
    }

    return {
      status: 'available',
      appTemplateId: upgradeable.appTemplateId,
      currentVersion: upgradeable.currentVersion,
      latestVersion: upgradeable.latestVersion,
      targetVersion: upgradeable.latestVersion,
    };
  }

  private async getLifecycleState(recordArg: IHostedAppLifecycleRecord, serviceArg: IService): Promise<IHostedAppLifecycleState> {
    const upgradeState = await this.getUpgradeState(serviceArg);
    const state: IHostedAppLifecycleState = {
      ...recordArg.state,
      appInstanceId: recordArg.appInstanceId,
      hostType: 'onebox',
      appName: recordArg.state.appName || serviceArg.name,
      publicUrl: recordArg.state.publicUrl || (serviceArg.domain ? `https://${serviceArg.domain}` : undefined),
      upgradeState,
    };
    await this.opsServerRef.oneboxRef.database.updateHostedAppLifecycleState(recordArg.serviceId, state);
    return state;
  }

  private async getLifecycleStateByServiceName(serviceNameArg: string): Promise<IHostedAppLifecycleState | undefined> {
    const service = await this.opsServerRef.oneboxRef.database.getServiceByName(serviceNameArg);
    if (!service?.id) {
      throw new plugins.typedrequest.TypedResponseError('Service not found');
    }
    const record = await this.opsServerRef.oneboxRef.database.getHostedAppLifecycleByServiceId(service.id);
    if (!record) {
      return undefined;
    }
    return await this.getLifecycleState(record, service);
  }

  private async updateLifecycleState(
    recordArg: IHostedAppLifecycleRecord,
    serviceArg: IService,
    stateArg: IHostedAppLifecycleState,
  ): Promise<IHostedAppLifecycleState> {
    const record = await this.opsServerRef.oneboxRef.database.updateHostedAppLifecycleState(recordArg.serviceId, stateArg);
    return await this.getLifecycleState(record, serviceArg);
  }

  private registerHandlers(): void {
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_HostedApp_ReportLifecycleState>(
        'hostedAppReportLifecycleState',
        async (dataArg) => {
          const { record, service } = await this.requireHostedAppIdentity(dataArg.identity);
          const state = await this.updateLifecycleState(record, service, {
            ...record.state,
            ...dataArg.report,
            appInstanceId: record.appInstanceId,
            hostType: 'onebox',
            reportedAt: Date.now(),
          });
          return { state };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_HostedApp_GetLifecycleState>(
        'hostedAppGetLifecycleState',
        async (dataArg) => {
          const { record, service } = await this.requireHostedAppIdentity(dataArg.identity);
          return { state: await this.getLifecycleState(record, service) };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_HostedApp_RequestBootstrapAction>(
        'hostedAppRequestBootstrapAction',
        async (dataArg) => {
          const { record, service } = await this.requireHostedAppIdentity(dataArg.identity);
          const now = Date.now();
          const action = {
            ...dataArg.action,
            id: dataArg.action.id || crypto.randomUUID(),
            status: 'ready' as const,
            label: dataArg.action.label || 'Initial setup',
            createdAt: now,
            updatedAt: now,
          };
          const state = await this.updateLifecycleState(record, service, {
            ...record.state,
            appInstanceId: record.appInstanceId,
            hostType: 'onebox',
            runtimeStatus: 'setupRequired',
            bootstrapAction: action,
            reportedAt: now,
          });
          return { action, state };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_HostedApp_CompleteBootstrapAction>(
        'hostedAppCompleteBootstrapAction',
        async (dataArg) => {
          const { record, service } = await this.requireHostedAppIdentity(dataArg.identity);
          const now = Date.now();
          const existingAction = record.state.bootstrapAction;
          const bootstrapAction = existingAction
            ? {
                ...existingAction,
                id: dataArg.actionId || existingAction.id,
                status: 'completed' as const,
                message: dataArg.message || existingAction.message,
                updatedAt: now,
                completedAt: now,
              }
            : undefined;
          const state = await this.updateLifecycleState(record, service, {
            ...record.state,
            appInstanceId: record.appInstanceId,
            hostType: 'onebox',
            runtimeStatus: record.state.runtimeStatus === 'setupRequired' ? 'running' : record.state.runtimeStatus,
            bootstrapAction,
            reportedAt: now,
          });
          return { state };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_HostedApp_StartManagedUpgrade>(
        'hostedAppStartManagedUpgrade',
        async (dataArg) => {
          const { record, service } = await this.requireHostedAppIdentity(dataArg.identity);
          const upgradeState = await this.getUpgradeState(service);
          const targetVersion = dataArg.targetVersion || upgradeState.targetVersion || upgradeState.latestVersion;
          if (!targetVersion) {
            throw new plugins.typedrequest.TypedResponseError('No managed upgrade target is available');
          }
          const operation = await this.opsServerRef.appStoreHandler.startHostedAppUpgrade(service.name, targetVersion);
          const nextUpgradeState: IHostedAppUpgradeState = {
            status: 'running',
            appTemplateId: operation.appTemplateId,
            currentVersion: operation.fromVersion,
            targetVersion: operation.targetVersion,
            operationId: operation.id,
            warnings: operation.warnings,
            startedAt: operation.startedAt,
            updatedAt: operation.updatedAt,
          };
          const state = await this.updateLifecycleState(record, service, {
            ...record.state,
            appInstanceId: record.appInstanceId,
            hostType: 'onebox',
            upgradeState: nextUpgradeState,
            reportedAt: Date.now(),
          });
          return { upgradeState: nextUpgradeState, state };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_HostedApp_GetManagedUpgradeStatus>(
        'hostedAppGetManagedUpgradeStatus',
        async (dataArg) => {
          const { service } = await this.requireHostedAppIdentity(dataArg.identity);
          return { upgradeState: await this.getUpgradeState(service) };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetHostedAppLifecycleForService>(
        'getHostedAppLifecycleForService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          return { state: await this.getLifecycleStateByServiceName(dataArg.serviceName) };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DismissHostedAppBootstrapAction>(
        'dismissHostedAppBootstrapAction',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.database.getServiceByName(dataArg.serviceName);
          if (!service?.id) {
            throw new plugins.typedrequest.TypedResponseError('Service not found');
          }
          const record = await this.opsServerRef.oneboxRef.database.getHostedAppLifecycleByServiceId(service.id);
          if (!record) {
            return { state: undefined };
          }
          const nextState = {
            ...record.state,
            appInstanceId: record.appInstanceId,
            hostType: 'onebox',
            bootstrapAction: undefined,
          };
          return { state: await this.updateLifecycleState(record, service, nextState) };
        },
      ),
    );
  }
}
