import { DbisError, ErrorCode } from '@/shared/types';
import { logger } from '@/infrastructure/monitoring/logger';

export interface LeiRegistryRecord {
  lei: string;
  legalName: string | null;
  legalCountry: string | null;
  registrationStatus: string | null;
  entityStatus: string | null;
  initialRegistrationDate: string | null;
  nextRenewalDate: string | null;
  managingLou: string | null;
}

export interface LeiValidationResult {
  normalizedLei: string;
  registryValidated: boolean;
  legalName: string | null;
  legalCountry: string | null;
  registrationStatus: string | null;
  entityStatus: string | null;
  initialRegistrationDate: string | null;
  nextRenewalDate: string | null;
  managingLou: string | null;
  validatedAt: string;
  source: 'gleif' | 'format-only';
}

interface RegistrationValidationRequest {
  lei?: string;
  institutionName: string;
  country: string;
}

function normalizeLei(input: string): string {
  return input.trim().toUpperCase().replace(/\s+/g, '');
}

function normalizeCountry(input: string): string {
  return input.trim().toUpperCase();
}

function normalizeEntityName(input: string): string {
  return input
    .trim()
    .toUpperCase()
    .replace(/&/g, ' AND ')
    .replace(/[^A-Z0-9]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function alphaNumericToIso7064Digits(input: string): string {
  return input
    .split('')
    .map((char) => {
      if (/[0-9]/.test(char)) {
        return char;
      }

      const code = char.charCodeAt(0);
      if (code >= 65 && code <= 90) {
        return String(code - 55);
      }

      throw new DbisError(ErrorCode.VALIDATION_ERROR, 'LEI contains invalid characters');
    })
    .join('');
}

function iso7064Mod97(value: string): number {
  let remainder = 0;

  for (const char of value) {
    remainder = (remainder * 10 + Number(char)) % 97;
  }

  return remainder;
}

function isValidLeiChecksum(lei: string): boolean {
  const rearranged = `${lei.slice(4)}${lei.slice(0, 4)}`;
  const numeric = alphaNumericToIso7064Digits(rearranged);
  return iso7064Mod97(numeric) === 1;
}

export class LeiValidationService {
  private readonly apiBaseUrl = (process.env.GLEIF_API_BASE_URL || 'https://api.gleif.org/api/v1').replace(/\/$/, '');
  private readonly timeoutMs = Number(process.env.DBIS_LEI_LOOKUP_TIMEOUT_MS || '5000');
  private readonly requireRegistryValidation = process.env.DBIS_REQUIRE_LEI_REGISTRY_VALIDATION !== 'false';
  private readonly allowFormatOnlyFallback = process.env.DBIS_ALLOW_LEI_FORMAT_ONLY_FALLBACK === 'true';

  validateLeiFormat(rawLei?: string): string {
    if (!rawLei?.trim()) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        'A Legal Entity Identifier (LEI) is required for new international financial entity registration'
      );
    }

    const lei = normalizeLei(rawLei);
    if (!/^[A-Z0-9]{20}$/.test(lei)) {
      throw new DbisError(ErrorCode.VALIDATION_ERROR, 'LEI must be exactly 20 alphanumeric characters');
    }

    return lei;
  }

  async validateRegistrationCandidate(
    request: RegistrationValidationRequest
  ): Promise<LeiValidationResult> {
    const normalizedLei = this.validateLeiFormat(request.lei);
    const requestedName = normalizeEntityName(request.institutionName);
    const requestedCountry = normalizeCountry(request.country);

    const registryRecord = await this.lookupLeiRecord(normalizedLei);
    if (!registryRecord) {
      if (this.requireRegistryValidation && !this.allowFormatOnlyFallback) {
        throw new DbisError(
          ErrorCode.VALIDATION_ERROR,
          `LEI ${normalizedLei} could not be validated against the registry`
        );
      }

      return {
        normalizedLei,
        registryValidated: false,
        legalName: null,
        legalCountry: null,
        registrationStatus: null,
        entityStatus: null,
        initialRegistrationDate: null,
        nextRenewalDate: null,
        managingLou: null,
        validatedAt: new Date().toISOString(),
        source: 'format-only',
      };
    }

    if (registryRecord.registrationStatus && registryRecord.registrationStatus !== 'ISSUED') {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        `LEI ${normalizedLei} is not currently issued`,
        { registrationStatus: registryRecord.registrationStatus }
      );
    }

    if (registryRecord.entityStatus && registryRecord.entityStatus !== 'ACTIVE') {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        `LEI ${normalizedLei} is not associated with an active legal entity`,
        { entityStatus: registryRecord.entityStatus }
      );
    }

    if (registryRecord.legalName) {
      const normalizedLegalName = normalizeEntityName(registryRecord.legalName);
      if (normalizedLegalName !== requestedName) {
        throw new DbisError(
          ErrorCode.VALIDATION_ERROR,
          'Registered party name does not match the LEI legal name',
          {
            providedName: request.institutionName,
            leiLegalName: registryRecord.legalName,
          }
        );
      }
    }

    if (registryRecord.legalCountry && normalizeCountry(registryRecord.legalCountry) !== requestedCountry) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        'Registered party country does not match the LEI legal address country',
        {
          providedCountry: requestedCountry,
          leiCountry: registryRecord.legalCountry,
        }
      );
    }

    return {
      normalizedLei,
      registryValidated: true,
      legalName: registryRecord.legalName,
      legalCountry: registryRecord.legalCountry,
      registrationStatus: registryRecord.registrationStatus,
      entityStatus: registryRecord.entityStatus,
      initialRegistrationDate: registryRecord.initialRegistrationDate,
      nextRenewalDate: registryRecord.nextRenewalDate,
      managingLou: registryRecord.managingLou,
      validatedAt: new Date().toISOString(),
      source: 'gleif',
    };
  }

  private async lookupLeiRecord(lei: string): Promise<LeiRegistryRecord | null> {
    const url = `${this.apiBaseUrl}/lei-records/${encodeURIComponent(lei)}`;

    try {
      const response = await fetch(url, {
        headers: { Accept: 'application/json' },
        signal: AbortSignal.timeout(this.timeoutMs),
      });

      if (response.status === 404) {
        return null;
      }

      if (!response.ok) {
        logger.warn('LEI registry lookup returned non-success status', {
          lei,
          status: response.status,
          statusText: response.statusText,
        });
        return null;
      }

      const payload = (await response.json()) as {
        data?: {
          id?: string;
          attributes?: {
            entity?: {
              legalName?: { name?: string | null };
              legalAddress?: { country?: string | null };
              status?: string | null;
            };
            registration?: {
              status?: string | null;
              initialRegistrationDate?: string | null;
              nextRenewalDate?: string | null;
              managingLou?: string | null;
            };
          };
        };
      };

      const attributes = payload.data?.attributes;
      return {
        lei: payload.data?.id || lei,
        legalName: attributes?.entity?.legalName?.name || null,
        legalCountry: attributes?.entity?.legalAddress?.country || null,
        registrationStatus: attributes?.registration?.status || null,
        entityStatus: attributes?.entity?.status || null,
        initialRegistrationDate: attributes?.registration?.initialRegistrationDate || null,
        nextRenewalDate: attributes?.registration?.nextRenewalDate || null,
        managingLou: attributes?.registration?.managingLou || null,
      };
    } catch (error) {
      logger.warn('LEI registry lookup failed', {
        lei,
        url,
        error: error instanceof Error ? error.message : 'Unknown error',
      });
      return null;
    }
  }
}

export const leiValidationService = new LeiValidationService();
