import * as plugins from '../../plugins.js';
import { logger } from '../../logging.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
import { requireAdminIdentity } from '../helpers/guards.js';
import { toApiData, toApiDataArray } from '../helpers/serialization.js';

const confidentialCredentialKeyPattern = /(password|secret|token|credential|key$|connection(?:string|url|uri)|uri)/i;

export const redactPlatformCredentials = (
  credentialsArg: Record<string, unknown>,
): Record<string, string> => {
  const confidentialValues = Object.entries(credentialsArg)
    .filter(([keyArg]) => confidentialCredentialKeyPattern.test(keyArg))
    .map(([, valueArg]) => String(valueArg))
    .filter((valueArg) => valueArg.length > 0);

  return Object.entries(credentialsArg).reduce((resultArg, [keyArg, valueArg]) => {
    const value = String(valueArg);
    const containsConfidentialValue = confidentialValues.some((credentialValueArg) => (
      value.includes(credentialValueArg)
    ));
    resultArg[keyArg] = confidentialCredentialKeyPattern.test(keyArg) || containsConfidentialValue
      ? '********'
      : value;
    return resultArg;
  }, {} as Record<string, string>);
};

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

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

  private registerHandlers(): void {
    // Get all services
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServices>(
        'getServices',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const services = await this.opsServerRef.oneboxRef.services.listServices();
          return { services: toApiDataArray(services) };
        },
      ),
    );

    // Get single service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetService>(
        'getService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          if (!service) {
            throw new plugins.typedrequest.TypedResponseError('Service not found');
          }
          return { service: toApiData(service) };
        },
      ),
    );

    // Create service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateService>(
        'createService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.services.deployService(dataArg.serviceConfig);
          return { service: toApiData(service) };
        },
      ),
    );

    // Update service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateService>(
        'updateService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.services.updateService(
            dataArg.serviceName,
            dataArg.updates,
          );
          return { service: toApiData(service) };
        },
      ),
    );

    // Delete service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteService>(
        'deleteService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          await this.opsServerRef.oneboxRef.services.removeService(dataArg.serviceName);
          return { ok: true };
        },
      ),
    );

    // Start service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartService>(
        'startService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          await this.opsServerRef.oneboxRef.services.startService(dataArg.serviceName);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          return { service: toApiData(service!) };
        },
      ),
    );

    // Stop service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopService>(
        'stopService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          await this.opsServerRef.oneboxRef.services.stopService(dataArg.serviceName);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          return { service: toApiData(service!) };
        },
      ),
    );

    // Restart service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RestartService>(
        'restartService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          await this.opsServerRef.oneboxRef.services.restartService(dataArg.serviceName);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          return { service: toApiData(service!) };
        },
      ),
    );

    // Get service logs
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceLogs>(
        'getServiceLogs',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const logs = await this.opsServerRef.oneboxRef.services.getServiceLogs(dataArg.serviceName);
          return { logs: String(logs) };
        },
      ),
    );

    // Get service stats
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceStats>(
        'getServiceStats',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          if (!service || !service.containerID) {
            throw new plugins.typedrequest.TypedResponseError('Service has no container');
          }
          const stats = await this.opsServerRef.oneboxRef.docker.getContainerStats(service.containerID);
          if (!stats) {
            throw new plugins.typedrequest.TypedResponseError('Could not retrieve container stats');
          }
          return { stats };
        },
      ),
    );

    // Get service metrics
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceMetrics>(
        'getServiceMetrics',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          if (!service || !service.id) {
            throw new plugins.typedrequest.TypedResponseError('Service not found');
          }
          const metrics = await this.opsServerRef.oneboxRef.database.getMetrics(service.id, dataArg.limit || 60);
          return { metrics: toApiDataArray(metrics) };
        },
      ),
    );

    // Get service platform resources
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServicePlatformResources>(
        'getServicePlatformResources',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const rawResources = await this.opsServerRef.oneboxRef.services.getServicePlatformResources(
            dataArg.serviceName,
          );
          const resources = rawResources.map((r: any) => ({
            id: r.resource.id,
            resourceType: r.resource.resourceType,
            resourceName: r.resource.resourceName,
            platformService: {
              type: r.platformService.type,
              name: r.platformService.name,
              status: r.platformService.status,
            },
            envVars: redactPlatformCredentials(r.credentials),
            createdAt: r.resource.createdAt,
          }));
          return { resources };
        },
      ),
    );

    // Get service backups
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackups>(
        'getServiceBackups',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const backups = await this.opsServerRef.oneboxRef.backupManager.listBackups(dataArg.serviceName);
          return { backups: toApiDataArray(backups) };
        },
      ),
    );

    // Create service backup
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateServiceBackup>(
        'createServiceBackup',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const result = await this.opsServerRef.oneboxRef.backupManager.createBackup(dataArg.serviceName);
          return { backup: toApiData(result.backup) };
        },
      ),
    );

    // Get service backup schedules
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetServiceBackupSchedules>(
        'getServiceBackupSchedules',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.services.getService(dataArg.serviceName);
          if (!service) {
            throw new plugins.typedrequest.TypedResponseError('Service not found');
          }
          const schedules = await this.opsServerRef.oneboxRef.backupScheduler.getSchedulesForService(
            dataArg.serviceName,
          );
          return { schedules: toApiDataArray(schedules) };
        },
      ),
    );
  }
}
