import {
  Injectable,
  NotFoundException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { PrismaService } from '@chatapp/database';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';

/**
 * FASE 6 — SAML SSO Service
 *
 * Gestiona la configuración SAML del workspace y maneja el callback ACS.
 * Implementación que no depende de librerías SAML externas: parsea el
 * Response SAML de forma básica para extraer atributos del usuario.
 */
@Injectable()
export class SamlSsoService {
  private readonly logger = new Logger(SamlSsoService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly config: ConfigService,
  ) {}

  /**
   * Configura o actualiza la integración SAML SSO del workspace.
   */
  async configure(wsId: string, userId: string, dto: {
    entity_id: string;
    sso_url: string;
    slo_url?: string;
    x509_cert: string;
    sp_entity_id?: string;
    acs_url?: string;
    name_id_format?: string;
    signing_algorithm?: string;
    default_role?: string;
    attribute_mapping?: any;
  }) {
    // Validar que no exista ya una configuración (o actualizarla)
    const existing = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });

    const data = {
      entityId: dto.entity_id,
      ssoUrl: dto.sso_url,
      sloUrl: dto.slo_url,
      x509Cert: dto.x509_cert,
      spEntityId: dto.sp_entity_id || `https://${this.config.get('APP_DOMAIN', 'localhost')}/api/v1/enterprise/saml/acs`,
      acsUrl: dto.acs_url || `https://${this.config.get('APP_DOMAIN', 'localhost')}/api/v1/enterprise/saml/acs`,
      nameIdFormat: dto.name_id_format || 'emailAddress',
      signingAlgorithm: dto.signing_algorithm || 'sha256',
      defaultRole: dto.default_role || 'member',
      attributeMapping: dto.attribute_mapping || {},
      isActive: true,
    };

    if (existing) {
      return this.prisma.samlConfig.update({
        where: { workspaceId: wsId },
        data,
      });
    }

    return this.prisma.samlConfig.create({
      data: {
        workspaceId: wsId,
        ...data,
        createdBy: userId,
      },
    });
  }

  /**
   * Devuelve la configuración SAML del workspace (sin datos sensibles).
   */
  async getConfig(wsId: string) {
    const config = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });
    if (!config) throw new NotFoundException('SAML SSO not configured');

    return {
      entity_id: config.entityId,
      sso_url: config.ssoUrl,
      slo_url: config.sloUrl,
      sp_entity_id: config.spEntityId,
      acs_url: config.acsUrl,
      name_id_format: config.nameIdFormat,
      signing_algorithm: config.signingAlgorithm,
      is_active: config.isActive,
      default_role: config.defaultRole,
      attribute_mapping: config.attributeMapping,
      cert_fingerprint: this.getCertFingerprint(config.x509Cert),
    };
  }

  /**
   * Activa o desactiva SSO SAML.
   */
  async toggle(wsId: string, active: boolean) {
    const config = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });
    if (!config) throw new NotFoundException('SAML SSO not configured');

    return this.prisma.samlConfig.update({
      where: { workspaceId: wsId },
      data: { isActive: active },
    });
  }

  /**
   * Elimina la configuración SAML del workspace.
   */
  async delete(wsId: string) {
    const config = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });
    if (!config) throw new NotFoundException('SAML SSO not configured');

    await this.prisma.samlConfig.delete({ where: { workspaceId: wsId } });
    return { deleted: true };
  }

  /**
   * Genera la URL de inicio de SSO (redirect al IdP).
   * Devuelve la URL SAML con un parámetro SAMLRequest codificado.
   */
  async initiateSso(wsId: string): Promise<{ redirect_url: string; relay_state: string }> {
    const config = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });
    if (!config || !config.isActive) {
      throw new BadRequestException('SAML SSO not configured or inactive');
    }

    // Generar un RelayState con el workspaceId para identificar el contexto al volver
    const relayState = Buffer.from(JSON.stringify({ wsId, ts: Date.now() })).toString('base64');

    // Construir un AuthnRequest SAML mínimo
    const samlRequest = this.buildAuthnRequest(config);
    const encoded = Buffer.from(samlRequest).toString('base64');

    const redirectUrl = `${config.ssoUrl}?SAMLRequest=${encodeURIComponent(encoded)}&RelayState=${encodeURIComponent(relayState)}`;

    return { redirect_url: redirectUrl, relay_state: relayState };
  }

  /**
   * Procesa el callback ACS del IdP.
   * Extrae el NameID y los atributos del SAML Response, busca o crea
   * el usuario, y devuelve un token JWT.
   */
  async handleAcsCallback(
    samlResponse: string,
    relayState: string,
  ): Promise<{
    workspace_id: string;
    user_id: string;
    email: string;
    display_name: string;
    is_new_user: boolean;
  }> {
    // Decodificar relayState para obtener wsId
    let wsId: string;
    try {
      const decoded = JSON.parse(Buffer.from(relayState, 'base64').toString());
      wsId = decoded.wsId;
    } catch {
      throw new BadRequestException('Invalid RelayState');
    }

    const config = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });
    if (!config || !config.isActive) {
      throw new BadRequestException('SAML SSO not configured or inactive');
    }

    // Parsear el SAML Response (implementación simplificada)
    const decoded = this.decodeSamlResponse(samlResponse);
    const email = this.extractNameId(decoded) || '';
    const attributes = this.extractAttributes(decoded);

    if (!email) {
      throw new BadRequestException('Could not extract email from SAML response');
    }

    // Buscar o crear el usuario
    let user = await this.prisma.user.findUnique({ where: { email } });
    let isNewUser = false;

    if (!user) {
      // Crear el usuario desde los atributos SAML
      const displayName = attributes.displayName || attributes.cn || email.split('@')[0];
      const realName = attributes.givenName && attributes.sn
        ? `${attributes.givenName} ${attributes.sn}`
        : displayName;

      user = await this.prisma.user.create({
        data: {
          email,
          displayName,
          realName,
          emailVerified: true,
          locale: 'es',
        },
      });

      // Crear UserIdentity para rastrear el proveedor SAML
      await this.prisma.userIdentity.create({
        data: {
          userId: user.id,
          provider: 'saml',
          providerUserId: email,
          providerEmail: email,
          providerData: attributes,
        },
      });

      // Añadir como miembro del workspace
      await this.prisma.workspaceMember.create({
        data: {
          workspaceId: wsId,
          userId: user.id,
          role: config.defaultRole,
        },
      });

      isNewUser = true;
    } else {
      // Verificar que el usuario es miembro del workspace
      const member = await this.prisma.workspaceMember.findUnique({
        where: { workspaceId_userId: { workspaceId: wsId, userId: user.id } },
      });
      if (!member) {
        await this.prisma.workspaceMember.create({
          data: {
            workspaceId: wsId,
            userId: user.id,
            role: config.defaultRole,
          },
        });
        isNewUser = true;
      }
    }

    // Actualizar identidad SAML si no existe
    const identity = await this.prisma.userIdentity.findUnique({
      where: {
        provider_providerUserId: { provider: 'saml', providerUserId: email },
      },
    });
    if (!identity) {
      await this.prisma.userIdentity.create({
        data: {
          userId: user.id,
          provider: 'saml',
          providerUserId: email,
          providerEmail: email,
          providerData: attributes,
        },
      });
    }

    // Log de auditoría
    await this.prisma.auditLog.create({
      data: {
        workspaceId: wsId,
        actorId: user.id,
        actorType: 'user',
        action: isNewUser ? 'sso.user_provisioned' : 'sso.user_login',
        entityType: 'user',
        entityId: user.id,
        details: { provider: 'saml', email },
      },
    });

    return {
      workspace_id: wsId,
      user_id: user.id,
      email,
      display_name: user.displayName,
      is_new_user: isNewUser,
    };
  }

  /**
   * Genera los metadatos del Service Provider para configurar el IdP.
   */
  async getServiceProviderMetadata(wsId: string): Promise<string> {
    const config = await this.prisma.samlConfig.findUnique({
      where: { workspaceId: wsId },
    });
    if (!config) throw new NotFoundException('SAML SSO not configured');

    const entity = config.spEntityId;
    const acs = config.acsUrl;
    const slo = config.sloUrl || '';

    return `<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="${entity}">
  <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:${config.nameIdFormat}</md:NameIDFormat>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="${acs}" index="0" isDefault="true"/>
    ${slo ? `<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="${slo}"/>` : ''}
  </md:SPSSODescriptor>
</md:EntityDescriptor>`;
  }

  // ------------------------------------------------------------------

  private buildAuthnRequest(config: any): string {
    const id = `id${crypto.randomBytes(16).toString('hex')}`;
    const issueInstant = new Date().toISOString();

    return `<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="${id}" Version="2.0" IssueInstant="${issueInstant}" Destination="${config.ssoUrl}" AssertionConsumerServiceURL="${config.acsUrl}">
  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">${config.spEntityId}</saml:Issuer>
  <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:${config.nameIdFormat}" AllowCreate="true"/>
</samlp:AuthnRequest>`;
  }

  private decodeSamlResponse(samlResponse: string): string {
    try {
      // Intentar base64
      return Buffer.from(samlResponse, 'base64').toString('utf8');
    } catch {
      // Asumir que ya es XML
      return samlResponse;
    }
  }

  private extractNameId(xml: string): string | null {
    const match = xml.match(/<saml:NameID[^>]*>([^<]+)<\/saml:NameID>/);
    return match ? match[1] : null;
  }

  private extractAttributes(xml: string): Record<string, string> {
    const attrs: Record<string, string> = {};
    const regex = /<saml:Attribute\s+Name="([^"]+)"[^>]*>\s*<saml:AttributeValue[^>]*>([^<]+)<\/saml:AttributeValue>\s*<\/saml:Attribute>/g;
    let match;
    while ((match = regex.exec(xml)) !== null) {
      const name = match[1];
      const value = match[2];
      // Mapear nombres comunes
      const key = name.includes('/') ? name.split('/').pop()!.toLowerCase() : name.toLowerCase();
      attrs[key] = value;
    }
    return attrs;
  }

  private getCertFingerprint(cert: string): string {
    try {
      const clean = cert.replace(/-----BEGIN CERTIFICATE-----/g, '').replace(/-----END CERTIFICATE-----/g, '').replace(/\s/g, '');
      const buf = Buffer.from(clean, 'base64');
      return crypto.createHash('sha1').update(buf).digest('hex').match(/.{2}/g)?.join(':') ?? '';
    } catch {
      return '';
    }
  }
}