import jwt from 'jsonwebtoken';
import type { SignOptions } from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';
import prisma from '@/shared/database/prisma';
import { getEnv } from '@/shared/config/env-validator';
import { JwtPayload } from '@/shared/types';
import { adminPermissionsService } from '@/core/admin/shared/admin-permissions.service';
import { leiValidationService } from '@/core/nostro-vostro/lei-validation.service';
import { portalSecurityService } from './portal-security.service';

export interface PortalLoginRequest {
  username: string;
  password: string;
  otp?: string;
  surface?: 'admin' | 'member' | 'core';
}

export interface PortalUser {
  id: string;
  employeeId: string;
  name: string;
  email: string;
  role: string;
  sovereignBankId?: string;
  permissions: string[];
}

export interface PortalAuthSuccessResult {
  user: PortalUser;
  token: string;
  mfaRequired?: false;
  passwordRotationRequired?: boolean;
}

export interface PortalAuthChallengeResult {
  mfaRequired: true;
  method: 'totp';
  passwordRotationRequired?: boolean;
  user: Pick<PortalUser, 'id' | 'employeeId' | 'name' | 'email' | 'role'>;
}

export type PortalAuthResult = PortalAuthSuccessResult | PortalAuthChallengeResult;

export interface PasswordResetTokenResult {
  accountType: 'employee' | 'member';
  identifier: string;
  resetToken: string;
  expiresAt: Date;
}

interface IssueEmployeeAccountRequest {
  employeeId: string;
  employeeName: string;
  email: string;
  roleName: string;
  securityClearance: string;
  password: string;
  mustRotatePassword?: boolean;
}

interface IssueMemberAccountRequest {
  memberId: string;
  memberName: string;
  email: string;
  institutionName: string;
  institutionCountry: string;
  participantId?: string;
  lei?: string;
  sovereignBankId?: string;
  password: string;
  mustRotatePassword?: boolean;
}

type ScopeMap = Record<string, string>;

function parseScopeMap(): ScopeMap {
  const raw = process.env.DBIS_PORTAL_EMPLOYEE_SCOPE_JSON?.trim();
  if (!raw) {
    return {};
  }

  try {
    const parsed = JSON.parse(raw) as unknown;
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
      return {};
    }
    return Object.fromEntries(
      Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
    );
  } catch {
    return {};
  }
}

function isLocked(lockedUntil?: Date | null): boolean {
  return Boolean(lockedUntil && lockedUntil.getTime() > Date.now());
}

function formatLockMessage(lockedUntil?: Date | null): string {
  if (!lockedUntil) {
    return 'Account is locked';
  }
  return `Account is locked until ${lockedUntil.toISOString()}`;
}

function readLeiValidation(metadata: unknown): { registryValidated?: boolean; source?: string } | null {
  if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
    return null;
  }

  const candidate = (metadata as Record<string, unknown>).leiValidation;
  if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
    return null;
  }

  return candidate as { registryValidated?: boolean; source?: string };
}

export class PortalAuthService {
  private readonly maxFailedAttempts = Number(process.env.DBIS_PORTAL_MAX_FAILED_ATTEMPTS || '5');
  private readonly lockoutMinutes = Number(process.env.DBIS_PORTAL_LOCKOUT_MINUTES || '30');
  private readonly resetTokenTtlMinutes = Number(process.env.DBIS_PORTAL_RESET_TOKEN_TTL_MINUTES || '30');

  async login(request: PortalLoginRequest): Promise<PortalAuthResult> {
    const surface = request.surface ?? 'admin';

    if (surface === 'member') {
      return this.loginMemberSurface(request);
    }

    return this.loginEmployeeSurface(request, surface);
  }

  buildPortalUserFromClaims(payload: JwtPayload): PortalUser {
    return {
      id: payload.employeeId || payload.email || 'portal-user',
      employeeId: payload.employeeId || payload.email || 'portal-user',
      name: payload.name || 'Portal User',
      email: payload.email || '',
      role: payload.roleName || payload.apiRole || 'Unknown',
      sovereignBankId: payload.sovereignBankId,
      permissions: Array.isArray(payload.permissions) ? payload.permissions : [],
    };
  }

  async changePassword(params: {
    portalSurface?: 'admin' | 'member' | 'core';
    employeeId?: string;
    currentPassword: string;
    newPassword: string;
  }): Promise<void> {
    if (params.portalSurface === 'member') {
      const member = await prisma.portal_member_accounts.findFirst({
        where: {
          memberId: params.employeeId,
          status: 'active',
        },
      });

      if (!member) {
        throw new Error('Member account not found');
      }

      if (!portalSecurityService.verifyPassword(params.currentPassword, member.portalPasswordHash)) {
        throw new Error('Current password is incorrect');
      }

      await prisma.portal_member_accounts.update({
        where: { id: member.id },
        data: {
          portalPasswordHash: portalSecurityService.hashPassword(params.newPassword),
          mustRotatePassword: false,
          passwordChangedAt: new Date(),
          passwordResetTokenHash: null,
          passwordResetTokenExpiresAt: null,
          updatedAt: new Date(),
        },
      });
      return;
    }

    const employee = await prisma.employee_credentials.findFirst({
      where: {
        employeeId: params.employeeId,
        status: 'active',
      },
    });

    if (!employee) {
      throw new Error('Employee account not found');
    }

    if (!employee.portalPasswordHash) {
      throw new Error('Portal credential is not configured for this employee');
    }

    if (!portalSecurityService.verifyPassword(params.currentPassword, employee.portalPasswordHash)) {
      throw new Error('Current password is incorrect');
    }

    await prisma.employee_credentials.update({
      where: { id: employee.id },
      data: {
        portalPasswordHash: portalSecurityService.hashPassword(params.newPassword),
        mustRotatePassword: false,
        passwordChangedAt: new Date(),
        passwordResetTokenHash: null,
        passwordResetTokenExpiresAt: null,
        updatedAt: new Date(),
      },
    });
  }

  async issuePasswordResetToken(params: {
    accountType: 'employee' | 'member';
    identifier: string;
  }): Promise<PasswordResetTokenResult> {
    const token = portalSecurityService.generateResetToken();
    const expiresAt = new Date(Date.now() + this.resetTokenTtlMinutes * 60 * 1000);

    if (params.accountType === 'member') {
      const member = await prisma.portal_member_accounts.findFirst({
        where: {
          OR: [
            { memberId: params.identifier },
            { email: { equals: params.identifier, mode: 'insensitive' } },
          ],
        },
      });

      if (!member) {
        throw new Error('Member account not found');
      }

      await prisma.portal_member_accounts.update({
        where: { id: member.id },
        data: {
          passwordResetTokenHash: token.hash,
          passwordResetTokenExpiresAt: expiresAt,
          failedLoginAttempts: 0,
          lockedUntil: null,
          updatedAt: new Date(),
        },
      });

      return {
        accountType: 'member',
        identifier: member.memberId,
        resetToken: token.plaintext,
        expiresAt,
      };
    }

    const employee = await prisma.employee_credentials.findFirst({
      where: {
        OR: [
          { employeeId: params.identifier },
          { email: { equals: params.identifier, mode: 'insensitive' } },
        ],
      },
    });

    if (!employee) {
      throw new Error('Employee account not found');
    }

    await prisma.employee_credentials.update({
      where: { id: employee.id },
      data: {
        passwordResetTokenHash: token.hash,
        passwordResetTokenExpiresAt: expiresAt,
        failedLoginAttempts: 0,
        lockedUntil: null,
        updatedAt: new Date(),
      },
    });

    return {
      accountType: 'employee',
      identifier: employee.employeeId,
      resetToken: token.plaintext,
      expiresAt,
    };
  }

  async completePasswordReset(params: {
    surface?: 'admin' | 'member' | 'core';
    username: string;
    resetToken: string;
    newPassword: string;
  }): Promise<void> {
    const resetTokenHash = portalSecurityService.hashResetToken(params.resetToken);

    if (params.surface === 'member') {
      const member = await prisma.portal_member_accounts.findFirst({
        where: {
          OR: [
            { memberId: params.username.trim() },
            { email: { equals: params.username.trim(), mode: 'insensitive' } },
          ],
        },
      });

      if (
        !member ||
        member.passwordResetTokenHash !== resetTokenHash ||
        !member.passwordResetTokenExpiresAt ||
        member.passwordResetTokenExpiresAt.getTime() < Date.now()
      ) {
        throw new Error('Invalid or expired password reset token');
      }

      await prisma.portal_member_accounts.update({
        where: { id: member.id },
        data: {
          portalPasswordHash: portalSecurityService.hashPassword(params.newPassword),
          mustRotatePassword: false,
          failedLoginAttempts: 0,
          lockedUntil: null,
          passwordChangedAt: new Date(),
          passwordResetTokenHash: null,
          passwordResetTokenExpiresAt: null,
          updatedAt: new Date(),
        },
      });
      return;
    }

    const employee = await prisma.employee_credentials.findFirst({
      where: {
        OR: [
          { employeeId: params.username.trim() },
          { email: { equals: params.username.trim(), mode: 'insensitive' } },
        ],
      },
    });

    if (
      !employee ||
      employee.passwordResetTokenHash !== resetTokenHash ||
      !employee.passwordResetTokenExpiresAt ||
      employee.passwordResetTokenExpiresAt.getTime() < Date.now()
    ) {
      throw new Error('Invalid or expired password reset token');
    }

    await prisma.employee_credentials.update({
      where: { id: employee.id },
      data: {
        portalPasswordHash: portalSecurityService.hashPassword(params.newPassword),
        mustRotatePassword: false,
        failedLoginAttempts: 0,
        lockedUntil: null,
        passwordChangedAt: new Date(),
        passwordResetTokenHash: null,
        passwordResetTokenExpiresAt: null,
        updatedAt: new Date(),
      },
    });
  }

  async getEmployeeMfaStatus(employeeId: string): Promise<{ enabled: boolean; enrolledAt?: Date | null }> {
    const employee = await prisma.employee_credentials.findUnique({
      where: { employeeId },
      select: { mfaEnabled: true, mfaEnrolledAt: true },
    });

    if (!employee) {
      throw new Error('Employee account not found');
    }

    return {
      enabled: employee.mfaEnabled,
      enrolledAt: employee.mfaEnrolledAt,
    };
  }

  async beginEmployeeMfaEnrollment(employeeId: string): Promise<{ secret: string; otpauthUrl: string }> {
    const employee = await prisma.employee_credentials.findUnique({
      where: { employeeId },
      select: { employeeId: true, email: true, status: true },
    });

    if (!employee || employee.status !== 'active') {
      throw new Error('Employee account not found');
    }

    const secret = portalSecurityService.generateTotpSecret();
    return {
      secret,
      otpauthUrl: portalSecurityService.generateTotpUri(employee.email || employee.employeeId, secret),
    };
  }

  async enableEmployeeMfa(employeeId: string, secret: string, otp: string): Promise<void> {
    if (!portalSecurityService.verifyTotp(secret, otp)) {
      throw new Error('Invalid MFA verification code');
    }

    const encrypted = portalSecurityService.encryptMfaSecret(secret);
    await prisma.employee_credentials.update({
      where: { employeeId },
      data: {
        mfaEnabled: true,
        mfaSecretCiphertext: encrypted.ciphertext,
        mfaSecretIv: encrypted.iv,
        mfaSecretTag: encrypted.tag,
        mfaEnrolledAt: new Date(),
        updatedAt: new Date(),
      },
    });
  }

  async disableEmployeeMfa(employeeId: string, otp: string): Promise<void> {
    const employee = await prisma.employee_credentials.findUnique({
      where: { employeeId },
      select: {
        id: true,
        mfaEnabled: true,
        mfaSecretCiphertext: true,
        mfaSecretIv: true,
        mfaSecretTag: true,
      },
    });

    if (!employee || !employee.mfaEnabled) {
      throw new Error('MFA is not enabled for this account');
    }

    const secret = this.readEmployeeMfaSecret(employee);
    if (!portalSecurityService.verifyTotp(secret, otp)) {
      throw new Error('Invalid MFA verification code');
    }

    await prisma.employee_credentials.update({
      where: { id: employee.id },
      data: {
        mfaEnabled: false,
        mfaSecretCiphertext: null,
        mfaSecretIv: null,
        mfaSecretTag: null,
        mfaEnrolledAt: null,
        updatedAt: new Date(),
      },
    });
  }

  async issueEmployeeAccount(
    request: IssueEmployeeAccountRequest
  ): Promise<{ employeeId: string; email: string; roleName: string }> {
    const role = await prisma.dbis_roles.findFirst({
      where: { roleName: request.roleName },
    });

    if (!role) {
      throw new Error(`Role ${request.roleName} does not exist`);
    }

    const passwordHash = portalSecurityService.hashPassword(request.password);
    const existing = await prisma.employee_credentials.findFirst({
      where: {
        OR: [
          { employeeId: request.employeeId },
          { email: { equals: request.email, mode: 'insensitive' } },
        ],
      },
    });

    if (existing) {
      await prisma.employee_credentials.update({
        where: { id: existing.id },
        data: {
          employeeId: request.employeeId,
          employeeName: request.employeeName,
          email: request.email,
          roleId: role.id,
          securityClearance: request.securityClearance,
          portalPasswordHash: passwordHash,
          mustRotatePassword: Boolean(request.mustRotatePassword),
          status: 'active',
          revokedAt: null,
          passwordChangedAt: new Date(),
          passwordResetTokenHash: null,
          passwordResetTokenExpiresAt: null,
          failedLoginAttempts: 0,
          lockedUntil: null,
          updatedAt: new Date(),
        },
      });
    } else {
      await prisma.employee_credentials.create({
        data: {
          id: uuidv4(),
          employeeId: request.employeeId,
          employeeName: request.employeeName,
          email: request.email,
          roleId: role.id,
          securityClearance: request.securityClearance,
          portalPasswordHash: passwordHash,
          mustRotatePassword: Boolean(request.mustRotatePassword),
          status: 'active',
          passwordChangedAt: new Date(),
          updatedAt: new Date(),
        },
      });
    }

    return {
      employeeId: request.employeeId,
      email: request.email,
      roleName: request.roleName,
    };
  }

  async issueMemberAccount(
    request: IssueMemberAccountRequest,
    approvedByEmployeeId?: string
  ): Promise<{
    memberId: string;
    email: string;
    participantId: string;
    approvalStatus: string;
  }> {
    const institution = await this.resolveMemberInstitution(request);

    const passwordHash = portalSecurityService.hashPassword(request.password);
    const existing = await prisma.portal_member_accounts.findFirst({
      where: {
        OR: [
          { memberId: request.memberId },
          { email: { equals: request.email, mode: 'insensitive' } },
        ],
      },
    });

    const approvalStatus = approvedByEmployeeId ? 'approved' : 'pending';
    const approvedAt = approvedByEmployeeId ? new Date() : null;

    if (existing) {
      await prisma.portal_member_accounts.update({
        where: { id: existing.id },
        data: {
          memberId: request.memberId,
          memberName: request.memberName,
          email: request.email,
          institutionName: institution.institutionName,
          institutionCountry: institution.institutionCountry,
          participantId: institution.participantId,
          lei: institution.lei,
          sovereignBankId: institution.sovereignBankId,
          portalPasswordHash: passwordHash,
          approvalStatus,
          approvedAt,
          approvedByEmployeeId: approvedByEmployeeId || null,
          mustRotatePassword: Boolean(request.mustRotatePassword),
          status: 'active',
          revokedAt: null,
          failedLoginAttempts: 0,
          lockedUntil: null,
          passwordChangedAt: new Date(),
          passwordResetTokenHash: null,
          passwordResetTokenExpiresAt: null,
          updatedAt: new Date(),
        },
      });
    } else {
      await prisma.portal_member_accounts.create({
        data: {
          id: uuidv4(),
          memberId: request.memberId,
          memberName: request.memberName,
          email: request.email,
          institutionName: institution.institutionName,
          institutionCountry: institution.institutionCountry,
          participantId: institution.participantId,
          lei: institution.lei,
          sovereignBankId: institution.sovereignBankId,
          portalPasswordHash: passwordHash,
          approvalStatus,
          approvedAt,
          approvedByEmployeeId: approvedByEmployeeId || null,
          mustRotatePassword: Boolean(request.mustRotatePassword),
          status: 'active',
          passwordChangedAt: new Date(),
          updatedAt: new Date(),
        },
      });
    }

    return {
      memberId: request.memberId,
      email: request.email,
      participantId: institution.participantId || 'snapshot-only',
      approvalStatus,
    };
  }

  async approveMemberAccount(memberId: string, approvedByEmployeeId: string): Promise<void> {
    const member = await prisma.portal_member_accounts.findUnique({
      where: { memberId },
    });

    if (!member) {
      throw new Error('Member account not found');
    }

    const institution = await this.resolveStoredMemberInstitution(member);

    await prisma.portal_member_accounts.update({
      where: { id: member.id },
      data: {
        approvalStatus: 'approved',
        approvedAt: new Date(),
        approvedByEmployeeId,
        institutionName: institution.institutionName,
        institutionCountry: institution.institutionCountry,
        participantId: institution.participantId,
        lei: institution.lei,
        sovereignBankId: institution.sovereignBankId,
        updatedAt: new Date(),
      },
    });
  }

  async listMemberAccounts(filters?: {
    approvalStatus?: string;
    participantId?: string;
  }) {
    return prisma.portal_member_accounts.findMany({
      where: {
        ...(filters?.approvalStatus ? { approvalStatus: filters.approvalStatus } : {}),
        ...(filters?.participantId ? { participantId: filters.participantId } : {}),
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async deactivateAccount(params: {
    accountType: 'employee' | 'member';
    identifier: string;
  }): Promise<void> {
    if (params.accountType === 'member') {
      const member = await prisma.portal_member_accounts.findFirst({
        where: {
          OR: [{ memberId: params.identifier }, { email: { equals: params.identifier, mode: 'insensitive' } }],
        },
      });
      if (!member) {
        throw new Error('Member account not found');
      }

      await prisma.portal_member_accounts.update({
        where: { id: member.id },
        data: {
          status: 'inactive',
          revokedAt: new Date(),
          lockedUntil: null,
          updatedAt: new Date(),
        },
      });
      return;
    }

    const employee = await prisma.employee_credentials.findFirst({
      where: {
        OR: [{ employeeId: params.identifier }, { email: { equals: params.identifier, mode: 'insensitive' } }],
      },
    });
    if (!employee) {
      throw new Error('Employee account not found');
    }

    await prisma.employee_credentials.update({
      where: { id: employee.id },
      data: {
        status: 'inactive',
        revokedAt: new Date(),
        lockedUntil: null,
        updatedAt: new Date(),
      },
    });
  }

  private async loginEmployeeSurface(
    request: PortalLoginRequest,
    surface: 'admin' | 'core'
  ): Promise<PortalAuthResult> {
    const username = request.username.trim();
    const password = request.password;
    const employee = await prisma.employee_credentials.findFirst({
      where: {
        status: 'active',
        OR: [
          { employeeId: username },
          { email: { equals: username, mode: 'insensitive' } },
        ],
      },
      include: { dbis_roles: true },
    });

    if (!employee) {
      throw new Error('Invalid credentials');
    }

    if (employee.expiresAt && employee.expiresAt < new Date()) {
      throw new Error('Credential expired');
    }

    if (isLocked(employee.lockedUntil)) {
      throw new Error(formatLockMessage(employee.lockedUntil));
    }

    if (!employee.portalPasswordHash) {
      throw new Error('Portal credential is not configured for this employee');
    }

    const matchesPassword = portalSecurityService.verifyPassword(password, employee.portalPasswordHash);
    if (!matchesPassword) {
      await this.recordFailedEmployeeLogin(employee.id, employee.failedLoginAttempts);
      throw new Error('Invalid credentials');
    }

    if (employee.mfaEnabled) {
      const secret = this.readEmployeeMfaSecret(employee);
      if (!request.otp) {
        return {
          mfaRequired: true,
          method: 'totp',
          passwordRotationRequired: employee.mustRotatePassword,
          user: {
            id: employee.employeeId,
            employeeId: employee.employeeId,
            name: employee.employeeName,
            email: employee.email,
            role: employee.dbis_roles?.roleName || 'Unknown',
          },
        };
      }

      if (!portalSecurityService.verifyTotp(secret, request.otp)) {
        throw new Error('Invalid MFA verification code');
      }
    }

    const permissions = (await adminPermissionsService.getEmployeePermissions(employee.employeeId))
      .map((permission) => permission as string);
    const sovereignBankId = this.resolveSovereignBankScope(employee.employeeId, employee.email);
    const roleName = employee.dbis_roles?.roleName || 'Unknown';

    if (roleName.startsWith('SCB_') && !sovereignBankId) {
      throw new Error('No sovereign-bank scope configured for this employee');
    }

    await prisma.employee_credentials.update({
      where: { id: employee.id },
      data: {
        failedLoginAttempts: 0,
        lockedUntil: null,
        lastLoginAt: new Date(),
        updatedAt: new Date(),
      },
    });

    const user: PortalUser = {
      id: employee.employeeId,
      employeeId: employee.employeeId,
      name: employee.employeeName,
      email: employee.email,
      role: roleName,
      sovereignBankId: sovereignBankId || undefined,
      permissions,
    };

    return {
      user,
      token: this.signPortalToken({
        employeeId: employee.employeeId,
        email: employee.email,
        name: employee.employeeName,
        roleName,
        permissions,
        sovereignBankId: sovereignBankId || undefined,
        identityType: 'WEB_PORTAL',
        sessionType: 'portal',
        portalSurface: surface,
        apiRole: roleName,
      }),
      passwordRotationRequired: employee.mustRotatePassword,
    };
  }

  private async loginMemberSurface(
    request: PortalLoginRequest
  ): Promise<PortalAuthResult> {
    const username = request.username.trim();
    const member = await prisma.portal_member_accounts.findFirst({
      where: {
        status: 'active',
        OR: [
          { memberId: username },
          { email: { equals: username, mode: 'insensitive' } },
        ],
      },
    });

    if (!member) {
      throw new Error('Invalid credentials');
    }

    if (member.expiresAt && member.expiresAt < new Date()) {
      throw new Error('Credential expired');
    }

    if (isLocked(member.lockedUntil)) {
      throw new Error(formatLockMessage(member.lockedUntil));
    }

    if (member.approvalStatus !== 'approved') {
      throw new Error('Member account is pending institution approval');
    }

    if (!member.lei || !member.institutionName || !member.institutionCountry) {
      throw new Error('Member account is missing institution approval metadata');
    }

    const institution = await this.resolveStoredMemberInstitution(member);

    const matchesPassword = portalSecurityService.verifyPassword(request.password, member.portalPasswordHash);
    if (!matchesPassword) {
      await this.recordFailedMemberLogin(member.id, member.failedLoginAttempts);
      throw new Error('Invalid credentials');
    }

    await prisma.portal_member_accounts.update({
      where: { id: member.id },
      data: {
        failedLoginAttempts: 0,
        lockedUntil: null,
        lastLoginAt: new Date(),
        institutionName: institution.institutionName,
        institutionCountry: institution.institutionCountry,
        participantId: institution.participantId,
        lei: institution.lei,
        sovereignBankId: institution.sovereignBankId,
        updatedAt: new Date(),
      },
    });

    const user: PortalUser = {
      id: member.memberId,
      employeeId: member.memberId,
      name: member.memberName,
      email: member.email,
      role: 'DBIS_Member_User',
      sovereignBankId: institution.sovereignBankId || undefined,
      permissions: ['portal:access'],
    };

    return {
      user,
      token: this.signPortalToken({
        employeeId: user.employeeId,
        email: user.email,
        name: user.name,
        roleName: user.role,
        permissions: user.permissions,
        sovereignBankId: institution.sovereignBankId || undefined,
        identityType: 'WEB_PORTAL',
        sessionType: 'portal',
        portalSurface: 'member',
        apiRole: 'member',
      }),
      passwordRotationRequired: member.mustRotatePassword,
    };
  }

  private readEmployeeMfaSecret(employee: {
    mfaSecretCiphertext?: string | null;
    mfaSecretIv?: string | null;
    mfaSecretTag?: string | null;
  }): string {
    if (!employee.mfaSecretCiphertext || !employee.mfaSecretIv || !employee.mfaSecretTag) {
      throw new Error('MFA secret is not configured for this account');
    }

    return portalSecurityService.decryptMfaSecret(
      employee.mfaSecretCiphertext,
      employee.mfaSecretIv,
      employee.mfaSecretTag
    );
  }

  private async recordFailedEmployeeLogin(employeeRecordId: string, currentAttempts: number): Promise<void> {
    const nextAttempts = currentAttempts + 1;
    const shouldLock = nextAttempts >= this.maxFailedAttempts;
    await prisma.employee_credentials.update({
      where: { id: employeeRecordId },
      data: {
        failedLoginAttempts: nextAttempts,
        lockedUntil: shouldLock ? new Date(Date.now() + this.lockoutMinutes * 60 * 1000) : null,
        updatedAt: new Date(),
      },
    });
  }

  private async recordFailedMemberLogin(memberRecordId: string, currentAttempts: number): Promise<void> {
    const nextAttempts = currentAttempts + 1;
    const shouldLock = nextAttempts >= this.maxFailedAttempts;
    await prisma.portal_member_accounts.update({
      where: { id: memberRecordId },
      data: {
        failedLoginAttempts: nextAttempts,
        lockedUntil: shouldLock ? new Date(Date.now() + this.lockoutMinutes * 60 * 1000) : null,
        updatedAt: new Date(),
      },
    });
  }

  private resolveSovereignBankScope(employeeId: string, email: string): string | null {
    const scopeMap = parseScopeMap();
    return scopeMap[employeeId] || scopeMap[email.toLowerCase()] || null;
  }

  private async resolveMemberInstitution(request: IssueMemberAccountRequest): Promise<{
    institutionName: string;
    institutionCountry: string;
    participantId: string | null;
    lei: string;
    sovereignBankId: string | null;
  }> {
    if (request.participantId) {
      const participant = await prisma.nostro_vostro_participants.findUnique({
        where: { participantId: request.participantId },
      }).catch(() => null);

      if (participant?.status === 'active') {
        const leiValidation = readLeiValidation(participant.metadata);
        if (participant.lei && leiValidation?.registryValidated && leiValidation.source === 'gleif') {
          return {
            institutionName: participant.name,
            institutionCountry: participant.country,
            participantId: participant.participantId,
            lei: participant.lei,
            sovereignBankId: participant.sovereignBankId || null,
          };
        }
      }
    }

    const validation = await leiValidationService.validateRegistrationCandidate({
      lei: request.lei,
      institutionName: request.institutionName,
      country: request.institutionCountry,
    });

    return {
      institutionName: request.institutionName,
      institutionCountry: request.institutionCountry.toUpperCase(),
      participantId: request.participantId || null,
      lei: validation.normalizedLei,
      sovereignBankId: request.sovereignBankId || null,
    };
  }

  private async resolveStoredMemberInstitution(member: {
    participantId?: string | null;
    institutionName?: string | null;
    institutionCountry?: string | null;
    lei?: string | null;
    sovereignBankId?: string | null;
  }): Promise<{
    institutionName: string;
    institutionCountry: string;
    participantId: string | null;
    lei: string;
    sovereignBankId: string | null;
  }> {
    if (member.participantId) {
      const participant = await prisma.nostro_vostro_participants.findUnique({
        where: { participantId: member.participantId },
      }).catch(() => null);

      if (participant?.status === 'active') {
        const leiValidation = readLeiValidation(participant.metadata);
        if (participant.lei && leiValidation?.registryValidated && leiValidation.source === 'gleif') {
          return {
            institutionName: participant.name,
            institutionCountry: participant.country,
            participantId: participant.participantId,
            lei: participant.lei,
            sovereignBankId: participant.sovereignBankId || null,
          };
        }
      }
    }

    if (!member.institutionName || !member.institutionCountry || !member.lei) {
      throw new Error('Member institution record is incomplete');
    }

    const validation = await leiValidationService.validateRegistrationCandidate({
      lei: member.lei,
      institutionName: member.institutionName,
      country: member.institutionCountry,
    });

    return {
      institutionName: member.institutionName,
      institutionCountry: member.institutionCountry.toUpperCase(),
      participantId: member.participantId || null,
      lei: validation.normalizedLei,
      sovereignBankId: member.sovereignBankId || null,
    };
  }

  private signPortalToken(claims: JwtPayload): string {
    const jwtSecret = getEnv('JWT_SECRET');
    const signOptions: SignOptions = {
      expiresIn: (process.env.DBIS_PORTAL_TOKEN_TTL || '8h') as SignOptions['expiresIn'],
    };
    return jwt.sign(
      {
        ...claims,
        permissions: claims.permissions || [],
      },
      jwtSecret,
      signOptions
    );
  }
}

export const portalAuthService = new PortalAuthService();
