import { BadRequestException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';

@Injectable()
export class PublicApiCryptoService {
  private readonly key: Buffer;
  private readonly iv: Buffer;

  constructor(config: ConfigService) {
    const secret = config.get<string>('PUBLIC_CRYPTO_SECRET') || config.get<string>('PUBLIC_API_KEY') || '';
    const ivSecret = config.get<string>('PUBLIC_AES_IV') || config.get<string>('AES_IV') || 'offline-maps-api-iv';
    const cryptoPassword = secret.slice(4, 36);

    this.key = createHash('sha256').update(cryptoPassword).digest();
    this.iv = createHash('md5').update(ivSecret).digest();
  }

  encrypt(value: unknown): string {
    const plaintext = typeof value === 'string' ? value : JSON.stringify(value);
    const iv = randomBytes(16);
    const cipher = createCipheriv('aes-256-cbc', this.key, iv);
    const encrypted = Buffer.concat([cipher.update(String(plaintext), 'utf8'), cipher.final()]);
    return this.toBase64Url(Buffer.concat([iv, encrypted]));
  }

  decrypt<T = unknown>(payload: unknown): T {
    try {
      const decrypted = this.tryDecrypt(payload);
      if (decrypted === null) throw new Error('Unable to decrypt payload');
      return decrypted as T;
    } catch {
      throw new BadRequestException('Invalid encrypted payload');
    }
  }

  tryDecrypt(payload: unknown): unknown | null {
    const normalized = this.normalizePayload(payload);
    if (!normalized) return null;

    const encryptedPayload = Buffer.from(this.fromBase64Url(normalized), 'base64');
    if (!encryptedPayload.length) return null;

    try {
      const { iv, encrypted } = this.extractIvAndCiphertext(encryptedPayload);
      const decipher = createDecipheriv('aes-256-cbc', this.key, iv);
      const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
      return this.parseJsonIfPossible(decrypted);
    } catch {
      return null;
    }
  }

  private normalizePayload(payload: unknown): string | null {
    const extracted = this.extractPayloadValue(payload);
    if (extracted === null || extracted === undefined) return null;

    let value = String(extracted).trim();
    if (!value) return null;

    if (value.startsWith('"') && value.endsWith('"')) {
      value = value.slice(1, -1);
    }

    const decodedJson = this.parseJsonIfPossible(value);
    if (decodedJson && typeof decodedJson === 'object' && !Array.isArray(decodedJson)) {
      const nested = this.extractPayloadValue(decodedJson);
      if (nested !== null && nested !== undefined && nested !== decodedJson) {
        value = String(nested).trim();
      }
    }

    return value.replace(/[\r\n\t]/g, '').replace(/ /g, '+');
  }

  extractPayloadValue(payload: unknown): unknown {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return payload;
    const source = payload as Record<string, unknown>;
    return source.dataSet ?? source.payload ?? source.encrypted_data ?? source.response ?? source.data ?? null;
  }

  private parseJsonIfPossible(value: string): unknown {
    try {
      return JSON.parse(value);
    } catch {
      return value;
    }
  }

  private toBase64Url(value: Buffer): string {
    return value.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
  }

  private fromBase64Url(value: string): string {
    const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
    const padding = base64.length % 4;
    return padding > 0 ? `${base64}${'='.repeat(4 - padding)}` : base64;
  }

  private extractIvAndCiphertext(payload: Buffer): { iv: Buffer; encrypted: Buffer } {
    const dynamicCiphertextLength = payload.length - 16;
    if (dynamicCiphertextLength > 0 && dynamicCiphertextLength % 16 === 0) {
      return {
        iv: payload.subarray(0, 16),
        encrypted: payload.subarray(16),
      };
    }

    return {
      iv: this.iv,
      encrypted: payload,
    };
  }
}
