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

export class VpnHandler {
  constructor(private opsServerRef: OpsServer) {
    this.registerHandlers();
  }

  private registerHandlers(): void {
    const viewRouter = this.opsServerRef.viewRouter;
    const adminRouter = this.opsServerRef.adminRouter;

    // ---- Read endpoints (viewRouter — valid identity required via middleware) ----

    // Get all registered VPN clients
    viewRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnClients>(
        'getVpnClients',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { clients: [] };
          }
          const clients = manager.listClients().map((c) => ({
            clientId: c.clientId,
            enabled: c.enabled,
            targetProfileIds: c.targetProfileIds,
            description: c.description,
            assignedIp: c.assignedIp,
            createdAt: c.createdAt,
            updatedAt: c.updatedAt,
            expiresAt: c.expiresAt,
            destinationAllowList: c.destinationAllowList,
            destinationBlockList: c.destinationBlockList,
            useHostIp: c.useHostIp,
            useDhcp: c.useDhcp,
            staticIp: c.staticIp,
            forceVlan: c.forceVlan,
            vlanId: c.vlanId,
          }));
          return { clients };
        },
      ),
    );

    // Get VPN server status
    viewRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnStatus>(
        'getVpnStatus',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          const vpnConfig = this.opsServerRef.dcRouterRef.options.vpnConfig;
          if (!manager) {
            return {
              status: {
                running: false,
                subnet: vpnConfig?.subnet || '10.8.0.0/24',
                wgListenPort: vpnConfig?.wgListenPort ?? 51820,
                serverPublicKeys: null,
                registeredClients: 0,
                connectedClients: 0,
              },
            };
          }

          const connected = await manager.getConnectedClients();
          return {
            status: {
              running: manager.running,
              subnet: manager.getSubnet(),
              wgListenPort: vpnConfig?.wgListenPort ?? 51820,
              serverPublicKeys: manager.getServerPublicKeys(),
              registeredClients: manager.listClients().length,
              connectedClients: connected.length,
            },
          };
        },
      ),
    );

    // Get currently connected VPN clients
    viewRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnConnectedClients>(
        'getVpnConnectedClients',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { connectedClients: [] };
          }

          const connected = await manager.getConnectedClients();
          return {
            connectedClients: connected.map((c) => ({
              clientId: c.registeredClientId || c.clientId,
              assignedIp: c.assignedIp,
              connectedSince: c.connectedSince,
              bytesSent: c.bytesSent,
              bytesReceived: c.bytesReceived,
              transport: c.transportType,
              remoteAddr: c.remoteAddr,
              sourceIp: manager.getClientSourceIp(c.registeredClientId || c.clientId),
            })),
          };
        },
      ),
    );

    // ---- Write endpoints (adminRouter — admin identity required via middleware) ----

    // Create a new VPN client
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateVpnClient>(
        'createVpnClient',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            const bundle = await manager.createClient({
              clientId: dataArg.clientId,
              targetProfileIds: dataArg.targetProfileIds,
              description: dataArg.description,
              destinationAllowList: dataArg.destinationAllowList,
              destinationBlockList: dataArg.destinationBlockList,
              useHostIp: dataArg.useHostIp,
              useDhcp: dataArg.useDhcp,
              staticIp: dataArg.staticIp,
              forceVlan: dataArg.forceVlan,
              vlanId: dataArg.vlanId,
            });

            // Retrieve the persisted doc to get dcrouter-level fields
            const persistedClient = manager.listClients().find(
              (c) => c.clientId === bundle.entry.clientId,
            );

            return {
              success: true,
              client: {
                clientId: bundle.entry.clientId,
                enabled: bundle.entry.enabled ?? true,
                targetProfileIds: persistedClient?.targetProfileIds,
                description: bundle.entry.description,
                assignedIp: bundle.entry.assignedIp,
                createdAt: Date.now(),
                updatedAt: Date.now(),
                expiresAt: bundle.entry.expiresAt,
                destinationAllowList: persistedClient?.destinationAllowList,
                destinationBlockList: persistedClient?.destinationBlockList,
                useHostIp: persistedClient?.useHostIp,
                useDhcp: persistedClient?.useDhcp,
                staticIp: persistedClient?.staticIp,
                forceVlan: persistedClient?.forceVlan,
                vlanId: persistedClient?.vlanId,
              },
              wireguardConfig: bundle.wireguardConfig,
            };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Update a VPN client's metadata
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVpnClient>(
        'updateVpnClient',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            await manager.updateClient(dataArg.clientId, {
              description: dataArg.description,
              targetProfileIds: dataArg.targetProfileIds,
              destinationAllowList: dataArg.destinationAllowList,
              destinationBlockList: dataArg.destinationBlockList,
              useHostIp: dataArg.useHostIp,
              useDhcp: dataArg.useDhcp,
              staticIp: dataArg.staticIp,
              forceVlan: dataArg.forceVlan,
              vlanId: dataArg.vlanId,
            });
            return { success: true };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Delete a VPN client
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteVpnClient>(
        'deleteVpnClient',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            await manager.removeClient(dataArg.clientId);
            return { success: true };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Enable a VPN client
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_EnableVpnClient>(
        'enableVpnClient',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            await manager.enableClient(dataArg.clientId);
            return { success: true };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Disable a VPN client
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisableVpnClient>(
        'disableVpnClient',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            await manager.disableClient(dataArg.clientId);
            return { success: true };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Rotate a VPN client's keys
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RotateVpnClientKey>(
        'rotateVpnClientKey',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            const bundle = await manager.rotateClientKey(dataArg.clientId);
            return {
              success: true,
              wireguardConfig: bundle.wireguardConfig,
            };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Export a VPN client config
    adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ExportVpnClientConfig>(
        'exportVpnClientConfig',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, {
            scope: 'vpn:write',
            requireAdminIdentity: true,
          });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            const config = await manager.exportClientConfig(dataArg.clientId, dataArg.format);
            return { success: true, config };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );

    // Get telemetry for a specific VPN client
    viewRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVpnClientTelemetry>(
        'getVpnClientTelemetry',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'vpn:read' });
          const manager = this.opsServerRef.dcRouterRef.vpnManager;
          if (!manager) {
            return { success: false, message: 'VPN not configured' };
          }

          try {
            const telemetry = await manager.getClientTelemetry(dataArg.clientId);
            if (!telemetry) {
              return { success: false, message: 'Client not found or not connected' };
            }
            return {
              success: true,
              telemetry: {
                clientId: telemetry.clientId,
                assignedIp: telemetry.assignedIp,
                bytesSent: telemetry.bytesSent,
                bytesReceived: telemetry.bytesReceived,
                packetsDropped: telemetry.packetsDropped,
                bytesDropped: telemetry.bytesDropped,
                lastKeepaliveAt: telemetry.lastKeepaliveAt,
                keepalivesReceived: telemetry.keepalivesReceived,
                rateLimitBytesPerSec: telemetry.rateLimitBytesPerSec,
                burstBytes: telemetry.burstBytes,
              },
            };
          } catch (err: unknown) {
            return { success: false, message: (err as Error).message };
          }
        },
      ),
    );
  }
}
