import {
  Injectable,
  InternalServerErrorException,
  NotFoundException,
} from '@nestjs/common';
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import { PrismaService } from 'src/prisma/prisma.service';
import { envs } from 'src/config';
import { UpsertBancardConfigDto } from './dto/upsert-bancard-config.dto';

const ALGORITHM = 'aes-256-cbc';
const IV_LENGTH = 16;

function getEncryptionKey(): Buffer {
  const raw = envs.bancardEncryptionKey ?? '';
  if (raw.length !== 32) {
    throw new InternalServerErrorException('BANCARD_ENCRYPTION_KEY debe tener exactamente 32 caracteres');
  }
  return Buffer.from(raw, 'utf8');
}

function encrypt(text: string): string {
  const key = getEncryptionKey();
  const iv = randomBytes(IV_LENGTH);
  const cipher = createCipheriv(ALGORITHM, key, iv);
  const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
  return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
}

function decrypt(encrypted: string): string {
  const key = getEncryptionKey();
  const [ivHex, dataHex] = encrypted.split(':');
  const iv = Buffer.from(ivHex, 'hex');
  const data = Buffer.from(dataHex, 'hex');
  const decipher = createDecipheriv(ALGORITHM, key, iv);
  const decrypted = Buffer.concat([decipher.update(data), decipher.final()]);
  return decrypted.toString('utf8');
}

@Injectable()
export class BancardConfigService {
  constructor(private readonly prisma: PrismaService) {}

  async upsertConfig(empresaId: string, dto: UpsertBancardConfigDto) {
    const existing = await this.prisma.bancard_config.findUnique({ where: { empresa_id: empresaId } });

    let privateKeyEncrypted = existing?.private_key_encrypted ?? null;
    if (dto.private_key) {
      privateKeyEncrypted = encrypt(dto.private_key);
    }

    const data = {
      ambiente: dto.ambiente,
      public_key: dto.public_key,
      private_key_encrypted: privateKeyEncrypted,
      return_url_base: dto.return_url_base,
      cancel_url_base: dto.cancel_url_base,
      webhook_secret: dto.webhook_secret,
      cuenta_contable_por_liquidar: dto.cuenta_contable_por_liquidar ?? null,
      cuenta_contable_banco: dto.cuenta_contable_banco ?? null,
      cuenta_contable_comision: dto.cuenta_contable_comision ?? null,
      cuenta_contable_iva_comision: dto.cuenta_contable_iva_comision ?? null,
      porcentaje_comision_credito: dto.porcentaje_comision_credito,
      porcentaje_comision_debito: dto.porcentaje_comision_debito,
      timeout_segundos: dto.timeout_segundos,
      activo: dto.activo,
      updated_at: new Date(),
    };

    const cfg = await this.prisma.bancard_config.upsert({
      where: { empresa_id: empresaId },
      create: { empresa_id: empresaId, ...data },
      update: data,
    });
    const { private_key_encrypted, ...rest } = cfg;
    return { ...rest, tiene_private_key: !!private_key_encrypted };
  }

  async getConfig(empresaId: string) {
    const cfg = await this.prisma.bancard_config.findUnique({ where: { empresa_id: empresaId } });
    if (!cfg) return null;
    const { private_key_encrypted, ...rest } = cfg;
    return { ...rest, tiene_private_key: !!private_key_encrypted };
  }

  async getPrivateKey(empresaId: string): Promise<string> {
    const cfg = await this.prisma.bancard_config.findUnique({ where: { empresa_id: empresaId } });
    if (!cfg?.private_key_encrypted) {
      throw new NotFoundException('Configuración Bancard no encontrada o sin clave privada');
    }
    return decrypt(cfg.private_key_encrypted);
  }

  async getPublicKey(empresaId: string): Promise<string> {
    const cfg = await this.prisma.bancard_config.findUnique({ where: { empresa_id: empresaId } });
    if (!cfg?.public_key) {
      throw new NotFoundException('Configuración Bancard no encontrada');
    }
    return cfg.public_key;
  }

  async getFullConfig(empresaId: string) {
    const cfg = await this.prisma.bancard_config.findUnique({ where: { empresa_id: empresaId } });
    if (!cfg) throw new NotFoundException('Configuración Bancard no encontrada');
    return cfg;
  }

  async getBaseUrl(empresaId: string): Promise<string> {
    const cfg = await this.getFullConfig(empresaId);
    return cfg.ambiente === 'produccion'
      ? (envs.bancardVposUrlProd ?? 'https://vpos.infonet.com.py/vpos/api/0.3')
      : (envs.bancardVposUrlStaging ?? 'https://vpos.infonet.com.py:8888/vpos/api/0.3');
  }
}
