import { Request, Response } from "express";
import axios from "axios";
import https from "https";
import {
  banescoConfirmacionTransaccion,
  banescoConfirmacionTransaccionValidationSchema,
} from "../validations/banescoConfirmacionTransaccionValidation";
import {
  banescoVueltoP2P,
  banescoVueltoP2PValidationSchema,
} from "../validations/banescoVueltoP2PValidation";
import {
  banescoConsultaCuenta,
  banescoConsultaCuentaValidationSchema,
} from "../validations/banescoConsultaCuentaValidation";
import {
  banescoCargoEnCuentaPago,
  banescoCargoEnCuentaPagoValidationSchema,
} from "../validations/banescoCargoEnCuentaPagoValidation";
import {
  banescoConsultaTransaccion,
  banescoConsultaTransaccionValidationSchema,
} from "../validations/banescoConsultaTransaccionValidation";
import {
  banescoCargoEnCuentaLink,
  banescoCargoEnCuentaLinkValidationSchema,
} from "../validations/banescoCargoEnCuentaLinkValidation";
import * as env from "../config/env.config";
import User from "../models/User";
import BanescoVuelto from "../models/banescoVuelto";

const agent = new https.Agent({
  rejectUnauthorized: env.BANESCO_VALIDATE_CERT === "true", // Desactiva la validación de certificados
});

/**
 * Obtiene un token OAuth2 de Banesco.
 *
 * Soporta `grant_type: password` (con client_id/secret como credenciales)
 * y `grant_type: authorization_code` (con código de autorización y redirect_uri).
 * Para authorization_code agrega header `Authorization: Basic` con credenciales
 * codificadas en base64.
 *
 * @export
 * @async
 * @param {string} url - URL del endpoint OAuth2.
 * @param {string} client_id - Client ID de la aplicación.
 * @param {string} client_secret - Client secret.
 * @param {string} username - Usuario (usado como client_id en grant password).
 * @param {string} password - Contraseña (usada como client_secret en grant password).
 * @param {string} grant_type - Tipo de autenticación (`password` o `authorization_code`).
 * @param {string} [code] - Código de autorización (solo para authorization_code).
 * @param {string} [redirect_uri] - URI de redirección (solo para authorization_code).
 * @returns {Promise<{success: boolean, message: string, data: any}>}
 */
export const getToken = async (
  url: string,
  client_id: string,
  client_secret: string,
  username: string,
  password: string,
  grant_type: string,
  code?: string,
  redirect_uri?: string,
) => {
  try {
    // Construimos los headers básicos
    const headers: Record<string, string> = {
      "Content-Type": "application/x-www-form-urlencoded",
    };

    // Si es grant_type authorization_code, agregamos Authorization Basic
    if (grant_type === "authorization_code") {
      const creds = Buffer.from(`${client_id}:${client_secret}`).toString(
        "base64",
      );
      headers["Authorization"] = `Basic ${creds}`;
    }

    // Preparamos el cuerpo en x-www-form-urlencoded
    const params = new URLSearchParams();
    if (grant_type === "password") {
      params.append("grant_type", grant_type);
      params.append("username", client_id);
      params.append("password", client_secret);
      params.append("client_id", username);
      params.append("client_secret", password);
    } else if (grant_type === "authorization_code") {
      params.append("grant_type", grant_type);
      params.append("code", code!);
      params.append("redirect_uri", redirect_uri!);
    }

    // Hacemos la petición
    const response = await axios.post(url, params.toString(), {
      headers,
      httpsAgent: agent,
      validateStatus: () => true,
    });

    if (response.status === 200) {
      return {
        success: true,
        message: "Se ha obtenido un nuevo token exitosamente",
        data: response.data,
      };
    } else {
      return {
        success: false,
        message: "No se pudo obtener el token.",
        data: response.data,
      };
    }
  } catch (error) {
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error,
    };
  }
};

/**
 * Confirma una transacción con la API de Consulta de Banesco.
 *
 * Valida los datos con Joi, obtiene un token OAuth2 con grant_type password
 * y envía la solicitud de confirmación al endpoint de consulta.
 *
 * @export
 * @async
 * @param {banescoConfirmacionTransaccion} data - Datos de la transacción a confirmar.
 * @returns {Promise<{success: boolean, message: string, data: any}>}
 */
export const confirmarTransaccion = async (
  data: banescoConfirmacionTransaccion,
) => {
  try {
    // Validar los filtros con Joi
    const { error, value } =
      banescoConfirmacionTransaccionValidationSchema.validate(data, {
        abortEarly: false,
      });

    // Si hay errores de validación, lanzar una excepción
    if (error) {
      return {
        success: false,
        message: `Error de validación: ${error.details
          .map((d) => d.message)
          .join(", ")}`,
        data: null,
        error: error,
      };
    }

    //Obtengo el token de acceso
    const token = await getToken(
      env.BANESCO_CONSULTA_API_SSO!,
      env.BANESCO_CONSULTA_API_CLIENT_ID!,
      env.BANESCO_CONSULTA_API_CLIENT_SECRET!,
      env.BANESCO_CONSULTA_API_USER!,
      env.BANESCO_CONSULTA_API_PASSWORD!,
      "password",
      undefined,
      undefined,
    );

    if (!token.success) {
      return {
        success: false,
        message: "No se pudo obtener un token",
        data: token,
      };
    }

    const url = env.BANESCO_CONSULTA_API_URL!;

    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token.data.access_token}`,
    };

    const body = data;

    const response = await axios.post(url, body, {
      headers,
      httpsAgent: agent,
      validateStatus: () => true,
    });

    // console.log('consultar_transaccion');
    // console.log(response);

    if (response.status == 200) {
      return {
        success: true,
        message: "Exitoso",
        data: response.data,
      };
    } else {
      return {
        success: false,
        message: "Fallido",
        data: response.data,
      };
    }
  } catch (error) {
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error: error,
    };
  }
};

/**
 * Procesa un vuelto P2P (devolución) a través de Banesco.
 *
 * Valida los datos con Joi, obtiene token OAuth2 y ejecuta la transacción
 * de vuelto. Si es exitosa, persiste el registro en `BanescoVuelto` con
 * el número de referencia retornado.
 *
 * @export
 * @async
 * @param {banescoVueltoP2P} data - Datos de la transacción de vuelto.
 * @returns {Promise<{success: boolean, message: string, data: any}>}
 */
export const vueltoP2p = async (data: banescoVueltoP2P) => {
  try {
    // Validar los filtros con Joi
    const { error, value } = banescoVueltoP2PValidationSchema.validate(data, {
      abortEarly: false,
    });

    // Si hay errores de validación, lanzar una excepción
    if (error) {
      return {
        success: false,
        message: `Error de validación: ${error.details
          .map((d) => d.message)
          .join(", ")}`,
        data: null,
        error: error,
      };
    }

    //Obtengo el token de acceso
    const token = await getToken(
      env.BANESCO_VUELTO_API_SSO!,
      env.BANESCO_VUELTO_API_CLIENT_ID!,
      env.BANESCO_VUELTO_API_CLIENT_SECRET!,
      env.BANESCO_VUELTO_API_USER!,
      env.BANESCO_VUELTO_API_PASSWORD!,
      "password",
      undefined,
      undefined,
    );

    if (!token.success) {
      return {
        success: false,
        message: "No se pudo obtener un token",
        data: token,
      };
    }

    const url = env.BANESCO_VUELTO_API_URL!;

    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token.data.access_token}`,
    };

    const body = data;

    const response = await axios.post(url, body, {
      headers,
      httpsAgent: agent,
      validateStatus: () => true,
    });

    // console.log("vuelto_p2p");
    // console.log(response);

    if (response.status == 200) {
      const banescoVuelto = new BanescoVuelto({
        ...data.dataRequest,
        referenceNumber: response.data.dataResponse?.referenceNumber,
      });
      // console.log(banescoVuelto);
      const savedVuelto = await banescoVuelto.save();

      return {
        success: true,
        message: "Exitoso",
        data: savedVuelto,
      };
    } else {
      console.log(response);
      return {
        success: false,
        message: "Fallido",
        data: response.data,
      };
    }
  } catch (error) {
    // console.log(error);
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error: error,
    };
  }
};

/**
 * Genera el enlace OAuth de autorización para Cargo en Cuenta de Banesco.
 *
 * Construye la URL con los parámetros `response_type`, `client_id`,
 * `redirect_uri` y `scope` configurados en las variables de entorno.
 *
 * @export
 * @async
 * @returns {Promise<{success: boolean, message: string, data: {url: string}}>}
 */
export const cargoEnCuentaLink = async () => {
  try {
    const proxy_url = env.BANESCO_CARGO_EN_CUENTA_API_PROXY_URL!;
    const response_type = env.BANESCO_CARGO_EN_CUENTA_API_RESPONSE_TYPE!;
    const client_id = env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_ID!;
    const redirect_uri = env.BANESCO_CARGO_EN_CUENTA_API_CALLBACK_URL!;
    const scope = env.BANESCO_CARGO_EN_CUENTA_API_SCOPE!;

    const url =
      proxy_url +
      "?response_type=" +
      encodeURIComponent(response_type) +
      "&client_id=" +
      encodeURIComponent(client_id) +
      "&redirect_uri=" +
      encodeURIComponent(redirect_uri) +
      "&scope=" +
      encodeURIComponent(scope);

    return {
      success: true,
      message: "Link generado correctamente",
      data: {
        url: url,
      },
    };
  } catch (error) {
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error: error,
    };
  }
};

/**
 * Maneja el callback OAuth de Banesco y genera una página HTML de redirección.
 *
 * Retorna un HTML con el logo de PASTA y un mensaje de redirección a la app.
 * En caso de error, muestra una página con el mensaje de error.
 *
 * @export
 * @async
 * @param {Request} req
 * @param {Response} res
 * @returns {Promise<{success: boolean, message: string, data: {htmlContent: string}}>}
 */
export const callback = async (req: Request, res: Response) => {
  const API_WEBSITE_URL = env.API_WEBSITE_URL!;

  const generateHtml = (bodyContent: string) => `
    <!DOCTYPE html>
    <html lang="es">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Redirigiendo...</title>
      <link rel="icon" href="${API_WEBSITE_URL}/public/img/logo_pasta.png" type="image/x-icon">
      <style>
        body {
          font-family: Arial, sans-serif;
          text-align: center;
          padding: 50px;
          background-color: #ffffff;
        }
        .logo {
          width: 150px;
          margin-bottom: 20px;
        }
        .message {
          font-size: 16px;
          color: #333;
        }
        .error-message {
          font-size: 24px;
          color: red;
        }
         
      </style>
    </head>
    <body>
      ${bodyContent}
    </body>
    </html>
  `;

  try {
    // Contenido para el caso exitoso
    const successContent = `
          <img class="logo" src="${API_WEBSITE_URL}/public/img/logo_pasta.png" alt="PER CAPITAL Logo">
          <div class="message">Redirigiendo a la app de PASTA...</div>
        `;

    const htmlContent = generateHtml(successContent);

    return {
      success: true,
      message: "Callback ejecutado correctamente",
      data: {
        htmlContent: htmlContent,
      },
      error: null,
    };
  } catch (error) {
    // Contenido para el caso de error
    const errorContent = `
          <p class="error-message">Ha ocurrido un error: ${
            error || "Error desconocido"
          }</p>
        `;

    const htmlContent = generateHtml(errorContent);

    return {
      success: false,
      message: "Error al generar la página de redirección.",
      data: {
        htmlContent: htmlContent,
      },
      error,
    };
  }
};

/**
 * Consulta información de cuenta mediante Cargo en Cuenta de Banesco.
 *
 * Valida los datos con Joi, obtiene token OAuth2 con authorization_code
 * y consulta la cuenta del usuario. Incluye el access_token en la respuesta
 * para uso posterior en el flujo de pago.
 *
 * @export
 * @async
 * @param {banescoConsultaCuenta} data - Datos de consulta incluyendo código de autorización.
 * @returns {Promise<{success: boolean, message: string, data: any}>}
 */
export const consultaCuenta = async (data: banescoConsultaCuenta) => {
  try {
    // Validar los filtros con Joi
    const { error, value } = banescoConsultaCuentaValidationSchema.validate(
      data,
      { abortEarly: false },
    );

    // Si hay errores de validación, lanzar una excepción
    if (error) {
      return {
        success: false,
        message: `Error de validación: ${error.details
          .map((d) => d.message)
          .join(", ")}`,
        data: null,
        error: error,
      };
    }

    //Obtengo el token de acceso
    const token = await getToken(
      env.BANESCO_CARGO_EN_CUENTA_API_SSO!,
      env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_ID!,
      env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_SECRET!,
      env.BANESCO_CARGO_EN_CUENTA_API_USER!,
      env.BANESCO_CARGO_EN_CUENTA_API_PASSWORD!,
      "authorization_code",
      data.dataRequest.auth.code,
      env.BANESCO_CARGO_EN_CUENTA_API_CALLBACK_URL!,
    );

    if (!token.success) {
      return {
        success: false,
        message: "No se pudo obtener un token",
        data: null,
        error: token.message,
      };
    }

    const url = env.BANESCO_CARGO_EN_CUENTA_API_CONSULTA_DE_CUENTA_URL!;

    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token.data.access_token}`,
    };

    const body = data;
    // console.log(body);
    const response = await axios.post(url, body, {
      headers,
      httpsAgent: agent,
      validateStatus: () => true,
    });

    if (response.status == 200) {
      response.data.token = token.data.access_token;

      return {
        success: true,
        message: "Exitoso",
        data: response.data,
      };
    } else {
      return {
        success: false,
        message: "Fallido",
        data: response.data,
      };
    }
  } catch (error) {
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error: error,
    };
  }
};

/**
 * Ejecuta un pago mediante Cargo en Cuenta de Banesco.
 *
 * Valida los datos con Joi. Si el body incluye un `token` previo, lo usa
 * directamente; de lo contrario, obtiene uno nuevo con authorization_code.
 * Elimina el campo `auth` del request antes de enviarlo al endpoint de pago.
 *
 * @export
 * @async
 * @param {banescoCargoEnCuentaPago} data - Datos del pago incluyendo token o código de autorización.
 * @returns {Promise<{success: boolean, message: string, data: any}>}
 */
export const cargoEnCuentaPago = async (data: banescoCargoEnCuentaPago) => {
  try {
    // Validar los filtros con Joi
    const { error, value } = banescoCargoEnCuentaPagoValidationSchema.validate(
      data,
      { abortEarly: false },
    );

    // Si hay errores de validación, lanzar una excepción
    if (error) {
      return {
        success: false,
        message: `Error de validación: ${error.details
          .map((d) => d.message)
          .join(", ")}`,
        data: null,
        error: error,
      };
    }

    //Obtengo el token de acceso
    let token_used = "";
    if (data.dataRequest.auth?.token) {
      token_used = data.dataRequest.auth.token;
    } else {
      const token = await getToken(
        env.BANESCO_CARGO_EN_CUENTA_API_SSO!,
        env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_ID!,
        env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_SECRET!,
        env.BANESCO_CARGO_EN_CUENTA_API_USER!,
        env.BANESCO_CARGO_EN_CUENTA_API_PASSWORD!,
        "authorization_code",
        data.dataRequest.auth!.code,
        env.BANESCO_CARGO_EN_CUENTA_API_CALLBACK_URL!,
      );

      if (!token.success) {
        return {
          success: false,
          message: "No se pudo obtener un token banesco",
          data: token,
        };
      }

      token_used = token.data.access_token;
    }

    const url = env.BANESCO_CARGO_EN_CUENTA_API_CARGO_EN_CUENTA_PAGO_URL!;

    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token_used}`,
    };

    console.log(headers);

    if (data.dataRequest.auth) {
      //este campo se lo agrego yo, no va
      delete data.dataRequest.auth;
    }

    const body = data;

    const response = await axios.post(url, body, {
      headers,
      httpsAgent: agent,
      validateStatus: () => true,
    });

    if (response.status == 200) {
      return {
        success: true,
        message: "Exitoso",
        data: response.data,
      };
    } else {
      return {
        success: false,
        message: "Fallido",
        data: response.data,
      };
    }
  } catch (error) {
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error: error,
    };
  }
};

/**
 * Consulta los detalles de una transacción de Cargo en Cuenta en Banesco.
 *
 * Valida los datos con Joi, obtiene token OAuth2 con authorization_code
 * y consulta el estado de la transacción en el endpoint correspondiente.
 *
 * @export
 * @async
 * @param {banescoConsultaTransaccion} data - Datos de consulta incluyendo código de autorización.
 * @returns {Promise<{success: boolean, message: string, data: any}>}
 */
export const consultarTransaccion = async (
  data: banescoConsultaTransaccion,
) => {
  try {
    // Validar los filtros con Joi
    const { error, value } =
      banescoConsultaTransaccionValidationSchema.validate(data, {
        abortEarly: false,
      });

    // Si hay errores de validación, lanzar una excepción
    if (error) {
      return {
        success: false,
        message: `Error de validación: ${error.details
          .map((d) => d.message)
          .join(", ")}`,
        data: null,
        error: error,
      };
    }

    //Obtengo el token de acceso
    const token = await getToken(
      env.BANESCO_CARGO_EN_CUENTA_API_SSO!,
      env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_ID!,
      env.BANESCO_CARGO_EN_CUENTA_API_CLIENT_SECRET!,
      env.BANESCO_CARGO_EN_CUENTA_API_USER!,
      env.BANESCO_CARGO_EN_CUENTA_API_PASSWORD!,
      "authorization_code",
      data.dataRequest.auth.code,
      env.BANESCO_CARGO_EN_CUENTA_API_CALLBACK_URL!,
    );

    if (!token.success) {
      return {
        success: false,
        message: "No se pudo obtener un token",
        data: token,
      };
    }

    const url = env.BANESCO_CARGO_EN_CUENTA_API_CONSULTA_DE_TRANSACCION_URL!;

    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token.data.access_token}`,
    };

    const body = data;

    const response = await axios.post(url, body, {
      headers,
      httpsAgent: agent,
      validateStatus: () => true,
    });

    // console.log('consulta_cuenta');
    // console.log(response);

    if (response.status == 200) {
      return {
        success: true,
        message: "Exitoso",
        data: response.data,
      };
    } else {
      return {
        success: false,
        message: "Fallido",
        data: response.data,
      };
    }
  } catch (error) {
    return {
      success: false,
      message: "Ocurrió un error al procesar la solicitud.",
      data: null,
      error: error,
    };
  }
};
