const axios = require("axios");
const crypto = require("crypto");
const Bank = require("../models/Bank");
const Transaction = require("../models/Transaction");

class Banco100Service {
  static async getConfig() {
    const bank = await Bank.findByCode("0156");

    const config = await Bank.getConfig(bank.id);
    return {
      apiKey: config.api_key,
      apiSecret: config.api_secret,
      merchantId: config.merchant_id,
      terminalId: config.terminal_id,
      baseUrl:
        process.env.MOCK_MODE === "true"
          ? "http://localhost:3001"
          : "https://www8.100x100banco.com",
    };
  }

  static async generateSignature(config, endpoint, date, body) {
    try {
      // 1. Host URL
      const host = "www8.100x100banco.com";

      // 2. Endpoint
      const urlPath = endpoint;

      // 3. Generar hash SHA256 del body y convertirlo a Base64
      const bodyHash = crypto
        .createHash("sha256")
        .update(JSON.stringify(body))
        .digest("hex");
      const bodyHashBase64 = Buffer.from(bodyHash, "hex").toString("base64");

      // 4. Concatenar todo
      const signatureString = host + urlPath + date + bodyHashBase64;

      // 5. Cifrar con HMAC-SHA256 usando apiSecret
      const hmac = crypto.createHmac("sha256", config.apiSecret);
      hmac.update(signatureString);
      const signatureHex = hmac.digest("hex");
      const signatureBase64 = Buffer.from(signatureHex, "hex").toString(
        "base64"
      );

      return signatureBase64;
    } catch (error) {
      throw new Error(`Error generando firma: ${error.message}`);
    }
  }

  static async validateTransaction(transactionData) {
    try {
      const config = await this.getConfig();

      // PRIMERO: Verificar si ya existe una transacción con esta referencia
      const bank = await Bank.findByCode("0156");
      const existingCheck = await Transaction.checkExistingTransaction(
        transactionData.reference,
        bank.id
      );

      // Si ya existe y está completada, retornar inmediatamente
      if (existingCheck.exists && existingCheck.status === "completed") {
        console.log("✅ Pago 100% Banco ya procesado:", {
          reference: transactionData.reference,
          transactionId: existingCheck.id,
        });

        return {
          success: true,
          alreadyProcessed: true,
          data: {
            sMerchantId: config.merchantId,
            sTrxType: "300",
            sTrxId: `EXISTING_${transactionData.reference}`,
            sReferenceNo: transactionData.reference,
            sRespCode: "90",
            sRespDesc: "Pago ya fue procesado y verificado anteriormente",
            sTerminalId: config.terminalId,
            sReqId: Buffer.from(
              `existing_${transactionData.reference}`
            ).toString("base64"),
          },
          transactionId: existingCheck.id,
          message: "Este pago ya fue procesado exitosamente anteriormente"
        };
      }

      // Generar TrxId único (AAAAMMDDXXXXXXXX)
      const now = new Date();
      const datePart = now.toISOString().slice(0, 10).replace(/-/g, "");
      const randomPart = Math.random().toString().substring(2, 10);
      const sTrxId = `${datePart}${randomPart}`;

      // Mapear tipos de transacción
      const transactionTypeMap = {
        pago_movil: "300",
        transferencia_cuenta: "211",
        transferencia_telefono: "201",
      };

      const requestPayload = {
        sMerchantId: config.merchantId,
        sTrxId: sTrxId,
        sTrxType: transactionTypeMap[transactionData.transaction_type] || "300",
        sBankId: transactionData.bank_destination,
        sDocumentId: transactionData.payer_identification,
        sFromAcctNo: this.formatAccountNumber(
          transactionData.payer_account,
          transactionData.transaction_type
        ),
        sToAcctNo: this.formatAccountNumber(
          transactionData.beneficiary_account,
          transactionData.transaction_type
        ),
        nAmount: parseFloat(transactionData.amount),
        sReferenceNo: transactionData.reference,
        sDateTrx: transactionData.payment_date,
        sTerminalId: config.terminalId,
      };

      // Validar campos requeridos
      this.validateRequestPayload(requestPayload);

      // Generar fecha y firma
      const date = new Date().toUTCString();
      const signature = await this.generateSignature(
        config,
        "/100p2pCert/api/v1/ValTrxIn",
        date,
        requestPayload
      );

      const dbTransactionData = {
        reference: transactionData.reference,
        bank_id: bank.id,
        transaction_type: transactionData.transaction_type,
        amount: transactionData.amount,
        payer_identification: transactionData.payer_identification,
        beneficiary_identification: transactionData.beneficiary_identification,
        bank_origin: transactionData.bank_destination,
        payment_date: transactionData.payment_date,
        raw_request_data: requestPayload,
        bank_transaction_id: `B100_${sTrxId}`,
        status: existingCheck.exists ? "retrying" : "pending",
      };

      // Crear o obtener transacción
      const createResult = await Transaction.create(dbTransactionData);
      const transactionId = createResult.transactionId;

      // Si ya existía y estaba completada, no llamar al banco
      if (createResult.existing && createResult.status === "completed") {
        return {
          success: true,
          alreadyProcessed: true,
          data: {
            sMerchantId: config.merchantId,
            sTrxType: "300",
            sTrxId: sTrxId,
            sReferenceNo: transactionData.reference,
            sRespCode: "90",
            sRespDesc: "Este pago ya fue procesado anteriormente",
            sTerminalId: config.terminalId,
            sReqId: Buffer.from(`verified_${sTrxId}`).toString("base64"),
          },
          transactionId,
          message: "Este pago ya fue procesado y verificado anteriormente"
        };
      }

      // USAR ENDPOINT CORRECTO SEGÚN AMBIENTE
      const endpoint = "/100p2pCert/api/v1/ValTrxIn";

      // Realizar la petición
      const response = await axios.post(
        `${config.baseUrl}${endpoint}`,
        requestPayload,
        {
          headers: {
            "x-api-key": config.apiKey,
            date: date,
            "x-signature": signature,
            "Content-Type": "application/json",
          },
          timeout: 30000,
        }
      );

      // Actualizar transacción con respuesta
      await Transaction.updateStatus(
        transactionData.reference,
        response.data.sRespCode === "00" ? "completed" : "failed",
        {
          code: response.data.sRespCode,
          message: response.data.sRespDesc,
          bank_reference: response.data.sReferenceNo,
          req_id: response.data.sReqId,
        }
      );

      return {
        success: response.data.sRespCode === "00",
        alreadyProcessed: false,
        data: response.data,
        transactionId,
        message: response.data.sRespCode === "00" ? "Pago procesado exitosamente" : "Error procesando el pago",
        isRetry: existingCheck.exists,
      };
    } catch (error) {
      // Registrar error en la transacción
      if (transactionData.reference) {
        await Transaction.updateStatus(transactionData.reference, "failed", {
          code: "ERROR",
          message: error.message,
        });
      }

      throw error;
    }
  }

  static formatAccountNumber(accountNumber, transactionType) {
    if (transactionType === "pago_movil") {
      // Formato para pago móvil: 58XXXNNNNNNN
      if (accountNumber.startsWith("58")) {
        return accountNumber;
      } else {
        // Convertir de 04141234567 a 584141234567
        return "58" + accountNumber.replace("0", "");
      }
    } else {
      // Formato para transferencia: CCCCNNNNNNNNNNNNNNNN
      return accountNumber;
    }
  }

  static validateRequestPayload(payload) {
    const required = [
      "sMerchantId",
      "sTrxId",
      "sTrxType",
      "sBankId",
      "sDocumentId",
      "sFromAcctNo",
      "sToAcctNo",
      "nAmount",
      "sReferenceNo",
      "sDateTrx",
      "sTerminalId",
    ];

    for (const field of required) {
      if (!payload[field]) {
        throw new Error(`Campo requerido faltante: ${field}`);
      }
    }

    // Validar formato de identificación
    if (!/^[VJE]\d+$/.test(payload.sDocumentId)) {
      throw new Error(
        "Formato de identificación inválido. Debe comenzar con V, J o E"
      );
    }

    // Validar monto
    if (payload.nAmount <= 0) {
      throw new Error("El monto debe ser mayor a 0");
    }
  }

  static mapResponseCode(responseCode) {
    const codeMap = {
      "00": "Transacción Aprobada",
      "05": "Tiempo de respuesta excedido",
      10: "Transacción anulada",
      13: "Negada, monto inválido",
      14: "Número móvil receptor errado o no afiliado",
      15: "Código del Banco Receptor no existe",
      16: "Rechazos técnico",
      17: "Moneda no válida",
      18: "Número de decimales incorrecto",
      19: "El Código del producto es inválido o no existe",
      20: "Longitud del nombre invalida",
      21: "Cuenta cancelada",
      22: "Identificación de mensaje duplicado",
      23: "Monto de la transacción no permitido",
      24: "Operación duplicada",
      25: "Datos del cliente no corresponden a la cuenta",
      30: "Formato errado",
      39: "Saldo Insuficiente",
      41: "Servicio no activo o negada por el banco",
      51: "Fondos insuficientes",
      55: "TOKEN inválido",
      56: "Número de celular no coincide",
      62: "Cuenta restringida",
      80: "Documento de identificación errado",
      87: "Institución receptora no disponible",
      89: "Institución receptora no disponible",
      90: "Institución receptora no disponible",
      91: "Institución receptora no disponible",
      92: "Banco receptor no afiliado",
      94: "Excede el límite diario",
      99: "Tiempo de espera excedido",
      "-99": "Error general",
      111: "Identificación no corresponde al beneficiario",
      112: "Número de cuenta incorrecto",
      701: "Transacción inválida",
      702: "Moneda inválida",
      703: "Código de banco inválido",
      704: "Longitud inválida de número de teléfono",
      705: "Cod. país de número teléfono invalido",
      706: "Cod. operadora inválido",
      707: "Cod. nacionalidad inválido",
      708: "Id.transacción inválido",
      709: "Id. terminal inválido",
      710: "Clave aprobación inválida",
      711: "MerchantId inválido",
      801: "Comercio no registrado API",
      802: "Comercio no registrado como cliente",
      803: "Comercio no registrado pago móvil",
      804: "Terminal no registrado para C2P",
      805: "Terminal no registrado para cobro C2P",
      806: "Terminal no registrado para reverso C2P",
      807: "Error al generar referencia pago móvil",
      808: "Error al registrar req. pago móvil",
      809: "Cuenta no afiliada pago móvil",
      810: "TrxId duplicado",
      811: "TrxId invalido",
      812: "Error al consultar TrxId",
    };

    return codeMap[responseCode] || "Código de respuesta desconocido";
  }
}

module.exports = Banco100Service;
