import { randomUUID } from 'node:crypto';

import {
  ForbiddenException,
  Inject,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import type { FastifyReply } from 'fastify';

import { generateSecureToken, hashToken } from '@hexahost/auth';
import type {
  SsoConsumeResponse,
  WhmcsCreateSsoRequest,
  WhmcsCreateSsoResponse,
} from '@hexahost/contracts';
import type { AppConfig } from '@hexahost/config';

import { APP_CONFIG } from '../config/config.constants';
import { PrismaService } from '../prisma/prisma.service';

import { AuditService } from './audit.service';
import { resolveSsoTargetPath } from './sso-target.util';
import { SessionService } from './session.service';

const SSO_TICKET_TTL_MS = 60_000;

type InstallationRef = {
  id: string;
  integrationId: string;
};

@Injectable()
export class SsoService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly sessionService: SessionService,
    private readonly auditService: AuditService,
    @Inject(APP_CONFIG) private readonly config: AppConfig,
  ) {}

  async createWhmcsServiceSso(
    installation: InstallationRef,
    externalServiceId: string,
    input: WhmcsCreateSsoRequest,
    idempotencyKey: string,
  ): Promise<WhmcsCreateSsoResponse> {
    const existingOp = await this.prisma.integrationOperation.findUnique({
      where: { idempotencyKey },
    });

    if (existingOp?.result) {
      return existingOp.result as WhmcsCreateSsoResponse;
    }

    const serviceLink = await this.prisma.whmcsServiceLink.findUnique({
      where: {
        installationId_externalServiceId: {
          installationId: installation.id,
          externalServiceId,
        },
      },
      include: {
        server: true,
      },
    });

    if (!serviceLink) {
      throw new NotFoundException(`Unknown WHMCS service ${externalServiceId}`);
    }

    if (serviceLink.status === 'SUSPENDED' || serviceLink.status === 'TERMINATED') {
      throw new ForbiddenException('Service is not eligible for SSO');
    }

    if (serviceLink.server.billingSuspendedAt) {
      throw new ForbiddenException('Server is billing suspended');
    }

    const clientLink = await this.prisma.whmcsClientLink.findUnique({
      where: {
        installationId_externalClientId: {
          installationId: installation.id,
          externalClientId: input.externalClientId,
        },
      },
    });

    if (!clientLink || clientLink.userId !== serviceLink.server.userId) {
      throw new ForbiddenException('WHMCS client is not linked to this service');
    }

    const isAdminSso = input.kind === 'admin';
    const targetPath = resolveSsoTargetPath(
      input.targetPath,
      serviceLink.serverId,
    );

    const plainToken = generateSecureToken();
    const tokenHash = hashToken(plainToken);
    const expiresAt = new Date(Date.now() + SSO_TICKET_TTL_MS);
    const audience = this.config.APP_URL.replace(/\/$/, '');

    const ticket = await this.prisma.ssoTicket.create({
      data: {
        installationId: installation.id,
        tokenHash,
        kind: isAdminSso ? 'ADMIN' : 'SERVICE',
        userId: serviceLink.server.userId,
        serverId: serviceLink.serverId,
        externalServiceId,
        externalClientId: input.externalClientId,
        externalUserId: input.externalUserId,
        targetPath,
        issuer: installation.integrationId,
        audience,
        nonce: randomUUID(),
        expiresAt,
      },
    });

    const redirectUrl = `${audience}/auth/sso?ticket=${encodeURIComponent(plainToken)}`;

    const response: WhmcsCreateSsoResponse = {
      ticketId: ticket.id,
      redirectUrl,
      expiresAt: expiresAt.toISOString(),
      targetPath,
    };

    await this.prisma.integrationOperation.create({
      data: {
        installationId: installation.id,
        idempotencyKey,
        operation: isAdminSso ? 'sso:admin' : 'sso:service',
        externalRef: externalServiceId,
        status: 'COMPLETED',
        result: response,
      },
    });

    await this.auditService.record({
      action: isAdminSso ? 'whmcs.sso.admin_created' : 'whmcs.sso.service_created',
      userId: serviceLink.server.userId,
      entityType: 'game_server',
      entityId: serviceLink.serverId,
      metadata: {
        externalServiceId,
        externalClientId: input.externalClientId,
        externalUserId: input.externalUserId,
        ticketId: ticket.id,
        targetPath,
      },
    });

    return response;
  }

  async consumeTicket(
    ticket: string,
    reply: FastifyReply,
    ipAddress?: string,
    userAgent?: string,
  ): Promise<SsoConsumeResponse> {
    const tokenHash = hashToken(ticket);

    const redeemed = await this.prisma.$transaction(async (tx) => {
      const record = await tx.ssoTicket.findUnique({
        where: { tokenHash },
        include: {
          user: {
            include: {
              platformRoles: { include: { role: true } },
            },
          },
        },
      });

      if (!record || record.usedAt || record.expiresAt <= new Date()) {
        throw new UnauthorizedException('Invalid or expired SSO ticket');
      }

      if (record.audience !== this.config.APP_URL.replace(/\/$/, '')) {
        throw new UnauthorizedException('SSO ticket audience mismatch');
      }

      const updated = await tx.ssoTicket.updateMany({
        where: {
          id: record.id,
          usedAt: null,
          expiresAt: { gt: new Date() },
        },
        data: { usedAt: new Date() },
      });

      if (updated.count !== 1) {
        throw new UnauthorizedException('SSO ticket already used');
      }

      return record;
    });

    const isImpersonation = redeemed.kind === 'ADMIN';
    const impersonationMetadata = isImpersonation
      ? {
          source: 'whmcs',
          externalServiceId: redeemed.externalServiceId,
          externalClientId: redeemed.externalClientId,
          externalUserId: redeemed.externalUserId,
          impersonatorLabel: `WHMCS admin user ${redeemed.externalUserId ?? 'unknown'}`,
        }
      : undefined;

    const { token, expiresAt } = await this.sessionService.createSession(
      redeemed.userId,
      ipAddress,
      userAgent,
      {
        isImpersonation,
        impersonationMetadata,
      },
    );

    this.sessionService.setSessionCookie(reply, token, expiresAt);

    await this.auditService.record({
      action: isImpersonation ? 'whmcs.sso.admin_consumed' : 'whmcs.sso.service_consumed',
      userId: redeemed.userId,
      entityType: redeemed.serverId ? 'game_server' : 'user',
      entityId: redeemed.serverId ?? redeemed.userId,
      ipAddress,
      metadata: {
        ticketId: redeemed.id,
        externalServiceId: redeemed.externalServiceId,
        externalUserId: redeemed.externalUserId,
        targetPath: redeemed.targetPath,
        isImpersonation,
      },
    });

    return {
      redirectPath: redeemed.targetPath,
      user: this.sessionService.toAuthUserResponse(redeemed.user),
      impersonation: isImpersonation
        ? {
            isImpersonation: true,
            label:
              impersonationMetadata?.impersonatorLabel ??
              'Support impersonation session',
          }
        : undefined,
    };
  }
}
