package services

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
	"gorm.io/gorm"
)

// Modelo que refleja la tabla en la base de datos
type DlocalApi struct {
	ID                 uint `gorm:"primaryKey"`
	Modo               string
	AccessKeyID        string
	AccessKeySecret    string
	AccessKeyIDdev     string
	AccessKeySecretdev string
	UrlProd            string
	UrlDev             string
}

// Estructura de configuración para usar en la llamada
type dlocalConfig struct {
	Modo               string
	AccessKeyID        string
	AccessKeySecret    string
	AccessKeyIDdev     string
	AccessKeySecretdev string
	UrlProd            string
	UrlDev             string
}

// Obtiene la configuración desde la base de datos
func GetDlocalConfigFromDB(db *gorm.DB) (dlocalConfig, error) {
	var api DlocalApi
	if err := db.First(&api).Error; err != nil {
		return dlocalConfig{}, err
	}

	return dlocalConfig{
		Modo:               api.Modo,
		AccessKeyID:        api.AccessKeyID,
		AccessKeySecret:    api.AccessKeySecret,
		AccessKeyIDdev:     api.AccessKeyIDdev,
		AccessKeySecretdev: api.AccessKeySecretdev,
		UrlProd:            api.UrlProd,
		UrlDev:             api.UrlDev,
	}, nil
}

type PlanRequest struct {
	Name            string  `json:"name"`
	Description     string  `json:"description"`
	Country         string  `json:"country,omitempty"`
	Currency        string  `json:"currency"`
	Amount          float64 `json:"amount"`
	FrequencyType   string  `json:"frequency_type"`
	FrequencyValue  int     `json:"frequency_value,omitempty"`
	DayOfMonth      int     `json:"day_of_month,omitempty"`
	NotificationURL string  `json:"notification_url,omitempty"`
	SuccessURL      string  `json:"success_url"`
	BackURL         string  `json:"back_url"`
	ErrorURL        string  `json:"error_url"`
}

type PlanUpdated struct {
	Name            string  `json:"name"`             // Plan name
	Description     string  `json:"description"`      // Plan description
	Amount          float64 `json:"amount"`           // Plan amount
	NotificationURL string  `json:"notification_url"` // Webhook notification URL
	BackURL         string  `json:"back_url"`         // URL to return client to merchant site
	SuccessURL      string  `json:"success_url"`      // Redirect URL on payment success
	ErrorURL        string  `json:"error_url"`        // Redirect URL on payment error
}

type PagoRequest struct {
	Currency        string  `json:"currency"`
	Amount          float64 `json:"amount"`
	OrderID         int     `json:"order_id"`
	Description     string  `json:"description"`
	SuccessURL      string  `json:"success_url"`
	BackURL         string  `json:"back_url"`
	NotificationURL string  `json:"notification_url"`
}

// Llama al endpoint /v1/me de DLocal usando la configuración
func GetDLocalMe(cfg dlocalConfig) ([]byte, error) {
	// Determinar URL base según el modo
	baseURL := cfg.UrlDev
	AccessKeyID := cfg.AccessKeyIDdev
	AccessKeySecret := cfg.AccessKeySecretdev
	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		AccessKeyID = cfg.AccessKeyID
		AccessKeySecret = cfg.AccessKeySecret
	}

	// Construir token de autorización
	authToken := fmt.Sprintf("Bearer %s:%s", AccessKeyID, AccessKeySecret)

	// Crear la solicitud HTTP
	req, err := http.NewRequest("GET", baseURL+"/v1/me", bytes.NewBuffer([]byte("{}")))
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", authToken)

	// Ejecutar la solicitud
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// Leer la respuesta
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

// Función para crear el plan en DLocal
func CreatePlan(cfg models.DlocalApi, plan models.PlanRequest) ([]byte, error) {
	// Determinar la URL base según el modo
	baseURL := cfg.UrlDev
	AccessKeyID := cfg.AccessKeyIDdev
	AccessKeySecret := cfg.AccessKeySecretdev
	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		AccessKeyID = cfg.AccessKeyID
		AccessKeySecret = cfg.AccessKeySecret
	}
	// URL completa para el endpoint
	url := baseURL + "/v1/subscription/plan"

	// Construir el token de autorización
	authToken := fmt.Sprintf("Bearer %s:%s", AccessKeyID, AccessKeySecret)

	// Convertir el cuerpo de la solicitud a JSON
	requestBody, err := json.Marshal(plan)
	if err != nil {
		return nil, fmt.Errorf("error al convertir el cuerpo de la solicitud a JSON: %v", err)
	}

	// Crear la solicitud HTTP
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %v", err)
	}

	// Agregar los encabezados a la solicitud
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", authToken)

	// Configurar el cliente HTTP
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Ejecutar la solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %v", err)
	}
	defer resp.Body.Close()

	// Leer la respuesta
	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %v", err)
	}

	// Si la respuesta no es exitosa, devolver un error
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("error en la respuesta de DLocal: %s", string(responseBody))
	}

	// Devolver la respuesta exitosa
	return responseBody, nil
}

func SeePlan(cfg models.DlocalApi, planID string) ([]byte, error) {
	// Determinar la URL base según el modo
	baseURL := cfg.UrlDev
	accessKeyID := cfg.AccessKeyIDdev
	accessKeySecret := cfg.AccessKeySecretdev

	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		accessKeyID = cfg.AccessKeyID
		accessKeySecret = cfg.AccessKeySecret
	}

	// Validar planID (opcional: depende de tus requisitos)
	if planID == "" {
		return nil, fmt.Errorf("planID no puede estar vacío")
	}

	// Construir la URL completa para el endpoint
	url := fmt.Sprintf("%s/v1/subscription/plan/%s", baseURL, planID)

	// Construir el token de autorización
	authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret)

	// Crear la solicitud HTTP
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %v", err)
	}

	// Agregar los encabezados a la solicitud
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", authToken)

	// Configurar el cliente HTTP
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Ejecutar la solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %v", err)
	}
	defer resp.Body.Close()

	// Leer la respuesta
	responseBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %v", err)
	}

	// Si la respuesta no es exitosa, devolver un error
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("error en la respuesta de DLocal (%d): %s", resp.StatusCode, string(responseBody))
	}

	// Devolver la respuesta exitosa
	return responseBody, nil
}

func UpdatedPlan(cfg models.DlocalApi, plan PlanUpdated, planID string) ([]byte, error) {
	// Determinar la URL base según el modo
	baseURL := cfg.UrlDev
	accessKeyID := cfg.AccessKeyIDdev
	accessKeySecret := cfg.AccessKeySecretdev

	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		accessKeyID = cfg.AccessKeyID
		accessKeySecret = cfg.AccessKeySecret
	}

	// Construir la URL completa para el endpoint
	url := fmt.Sprintf("%s/v1/subscription/plan/%s", baseURL, planID)

	// Construir el token de autorización
	authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret)

	// Convertir el cuerpo de la solicitud a JSON
	requestBody, err := json.Marshal(plan)
	if err != nil {
		return nil, fmt.Errorf("error al convertir el cuerpo de la solicitud a JSON: %v", err)
	}

	// Crear la solicitud HTTP
	req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(requestBody))
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %v", err)
	}

	// Agregar los encabezados a la solicitud
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", authToken)

	// Configurar el cliente HTTP
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Ejecutar la solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %v", err)
	}
	defer resp.Body.Close()

	// Leer la respuesta
	responseBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %v", err)
	}

	// Si la respuesta no es exitosa, devolver un error
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("error en la respuesta de DLocal (%d): %s", resp.StatusCode, string(responseBody))
	}

	// Devolver la respuesta exitosa
	return responseBody, nil
}

func DeactivatePlan(cfg models.DlocalApi, planID string, subscriptionId string) ([]byte, error) {
	// Determinar la URL base según el modo
	baseURL := cfg.UrlDev
	accessKeyID := cfg.AccessKeyIDdev
	accessKeySecret := cfg.AccessKeySecretdev

	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		accessKeyID = cfg.AccessKeyID
		accessKeySecret = cfg.AccessKeySecret
	}

	// Construir la URL completa para el endpoint
	url := fmt.Sprintf("%s/v1/subscription/plan/%s/subscription/%s/deactivate", baseURL, planID, subscriptionId)

	// Construir el token de autorización
	authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret)

	// Crear la solicitud HTTP
	req, err := http.NewRequest("PATCH", url, nil)
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %v", err)
	}

	// Agregar los encabezados a la solicitud
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", authToken)

	// Configurar el cliente HTTP
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Ejecutar la solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %v", err)
	}
	defer resp.Body.Close()

	// Leer la respuesta
	responseBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %v", err)
	}

	// Si la respuesta no es exitosa, devolver un error
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("error en la respuesta de DLocal (%d): %s", resp.StatusCode, string(responseBody))
	}

	// Devolver la respuesta exitosa
	return responseBody, nil
}

func SeePlanes(cfg models.DlocalApi) ([]byte, error) {
	// Validar modo y establecer valores
	var baseURL, accessKeyID, accessKeySecret string

	switch cfg.Modo {
	case "prod":
		baseURL = cfg.UrlProd
		accessKeyID = cfg.AccessKeyID
		accessKeySecret = cfg.AccessKeySecret
	default:
		baseURL = cfg.UrlDev
		accessKeyID = cfg.AccessKeyIDdev
		accessKeySecret = cfg.AccessKeySecretdev
	}

	if baseURL == "" || accessKeyID == "" || accessKeySecret == "" {
		return nil, fmt.Errorf("configuración incompleta para el entorno %q", cfg.Modo)
	}

	// Construir la URL del endpoint
	url := fmt.Sprintf("%s/v1/subscription/plan/all", baseURL)

	// Crear la solicitud HTTP
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %w", err)
	}

	// Establecer encabezados
	req.Header.Set("Content-Type", "application/json")
	// En DLocal se usa generalmente Basic Auth codificado en base64, pero si realmente usan Bearer personalizado:
	authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret)
	req.Header.Set("Authorization", authToken)

	// Cliente HTTP con timeout
	client := &http.Client{Timeout: 30 * time.Second}

	// Ejecutar solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %w", err)
	}
	defer resp.Body.Close()

	// Leer respuesta
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("respuesta no exitosa (%d): %s", resp.StatusCode, string(body))
	}

	return body, nil
}

func SeeSubscription(cfg models.DlocalApi, subscriptionId string, invoiceId string) ([]byte, error) {
	// Determinar la URL base según el modo
	baseURL := cfg.UrlDev
	accessKeyID := cfg.AccessKeyIDdev
	accessKeySecret := cfg.AccessKeySecretdev

	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		accessKeyID = cfg.AccessKeyID
		accessKeySecret = cfg.AccessKeySecret
	}

	// Construir la URL completa para el endpoint
	url := fmt.Sprintf("%s/v1/subscription/%s/execution/%s", baseURL, subscriptionId, invoiceId)

	// Crear la solicitud HTTP
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %w", err)
	}

	// Establecer encabezados
	req.Header.Set("Content-Type", "application/json")
	// En DLocal se usa generalmente Basic Auth codificado en base64, pero si realmente usan Bearer personalizado:
	authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret)
	req.Header.Set("Authorization", authToken)

	// Cliente HTTP con timeout
	client := &http.Client{Timeout: 30 * time.Second}

	// Ejecutar solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %w", err)
	}
	defer resp.Body.Close()

	// Leer respuesta
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("respuesta no exitosa (%d): %s", resp.StatusCode, string(body))
	}

	return body, nil
}

func CreatePago(cfg models.DlocalApi, pago PagoRequest) ([]byte, error) {
	// Determinar la URL base y credenciales
	baseURL := cfg.UrlDev
	AccessKeyID := cfg.AccessKeyIDdev
	AccessKeySecret := cfg.AccessKeySecretdev
	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		AccessKeyID = cfg.AccessKeyID
		AccessKeySecret = cfg.AccessKeySecret
	}

	// URL del endpoint de pagos
	url := baseURL + "/v1/payments"

	// Token de autenticación
	authToken := fmt.Sprintf("Bearer %s:%s", AccessKeyID, AccessKeySecret)

	// Convertir el cuerpo de la solicitud a JSON
	requestBody, err := json.Marshal(pago)
	if err != nil {
		return nil, fmt.Errorf("error al convertir el cuerpo de la solicitud a JSON: %v", err)
	}

	// Crear la solicitud HTTP
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
	if err != nil {
		return nil, fmt.Errorf("error al crear la solicitud: %v", err)
	}

	// Encabezados
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", authToken)

	// Cliente HTTP con timeout
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Ejecutar la solicitud
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error al enviar la solicitud: %v", err)
	}
	defer resp.Body.Close()

	// Leer la respuesta
	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("error al leer la respuesta: %v", err)
	}

	// Verificar si la respuesta fue exitosa
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("error en la respuesta de DLocal: %s", string(responseBody))
	}

	return responseBody, nil
}

// ─── Webhook dLocal ───────────────────────────────────────────────────────────

// DlocalWebhookNotification es la estructura del JSON que dLocal envía en cada notificación.
type DlocalWebhookNotification struct {
	ID          string    `json:"id"`     // notification ID único
	Type        string    `json:"type"`   // PAYMENT, SUBSCRIPTION_CHARGE, etc.
	Status      string    `json:"status"` // PAID, PENDING, REJECTED, CANCELLED, EXPIRED, AUTHORIZED
	CreatedDate time.Time `json:"created_date"`
	Payment     struct {
		ID       string  `json:"id"`
		Amount   float64 `json:"amount"`
		Currency string  `json:"currency"`
		OrderID  string  `json:"order_id"`
		Payer    struct {
			Email string `json:"email"`
			Name  string `json:"name"`
		} `json:"payer"`
	} `json:"payment"`
	// Para notificaciones de suscripción
	Subscription struct {
		ID string `json:"id"`
	} `json:"subscription"`
	Invoice struct {
		ID       string  `json:"id"`
		Amount   float64 `json:"amount"`
		Currency string  `json:"currency"`
	} `json:"invoice"`
}

// ParseDlocalWebhookNotification parsea el body crudo en la estructura de notificación.
func ParseDlocalWebhookNotification(rawBody []byte) (*DlocalWebhookNotification, error) {
	var n DlocalWebhookNotification
	if err := json.Unmarshal(rawBody, &n); err != nil {
		return nil, err
	}
	return &n, nil
}

// VerifyDlocalSignature verifica la firma HMAC-SHA256 enviada por dLocal.
// dLocal calcula: HMAC-SHA256(secretKey, rawBody) → hex.
// El header es "X-dLocal-Signature".
func VerifyDlocalSignature(rawBody []byte, signature string, cfg models.DlocalApi) bool {
	secret := cfg.AccessKeySecretdev
	if cfg.Modo == "prod" {
		secret = cfg.AccessKeySecret
	}
	if secret == "" {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write(rawBody)
	expected := hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(signature))
}

// ─── Consulta directa de pagos dLocal ────────────────────────────────────────

// dlocalPaymentResp mapea la respuesta de GET /v1/payments?order_id=...
type dlocalPaymentResp struct {
	ID       string  `json:"id"`
	Status   string  `json:"status"` // PAID, PENDING, REJECTED, CANCELLED, EXPIRED, AUTHORIZED
	Amount   float64 `json:"amount"`
	Currency string  `json:"currency"`
	OrderID  string  `json:"order_id"`
	Payer    struct {
		Email string `json:"email"`
	} `json:"payer"`
}

type dlocalPaymentListResp struct {
	Data []dlocalPaymentResp `json:"data"`
}

// CheckDlocalPaymentByOrderID busca en la API de dLocal un pago con el order_id dado
// y devuelve (pagado bool, paymentID string, monto float64, payerEmail string, error).
func CheckDlocalPaymentByOrderID(cfg models.DlocalApi, orderID string) (bool, string, float64, string, error) {
	baseURL := cfg.UrlDev
	accessKeyID := cfg.AccessKeyIDdev
	accessKeySecret := cfg.AccessKeySecretdev
	if cfg.Modo == "prod" {
		baseURL = cfg.UrlProd
		accessKeyID = cfg.AccessKeyID
		accessKeySecret = cfg.AccessKeySecret
	}

	url := fmt.Sprintf("%s/v1/payments?order_id=%s", baseURL, orderID)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return false, "", 0, "", err
	}
	authToken := fmt.Sprintf("Bearer %s:%s", accessKeyID, accessKeySecret)
	req.Header.Set("Authorization", authToken)
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return false, "", 0, "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false, "", 0, "", err
	}
	if resp.StatusCode != http.StatusOK {
		return false, "", 0, "", fmt.Errorf("dlocal status %d: %s", resp.StatusCode, string(body))
	}

	// La respuesta puede ser lista o un objeto directo
	var list dlocalPaymentListResp
	if err := json.Unmarshal(body, &list); err == nil && len(list.Data) > 0 {
		for _, p := range list.Data {
			if p.Status == "PAID" || p.Status == "AUTHORIZED" {
				return true, p.ID, p.Amount, p.Payer.Email, nil
			}
		}
		return false, "", 0, "", nil
	}
	// Si no es lista, intentar objeto directo
	var single dlocalPaymentResp
	if err := json.Unmarshal(body, &single); err == nil && single.ID != "" {
		paid := single.Status == "PAID" || single.Status == "AUTHORIZED"
		return paid, single.ID, single.Amount, single.Payer.Email, nil
	}
	return false, "", 0, "", nil
}
