package services

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

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

// ─── Endpoints de PayPal ──────────────────────────────────────────────────────

const (
	paypalBaseSandbox = "https://api-m.sandbox.paypal.com"
	paypalBaseLive    = "https://api-m.paypal.com"
)

// paypalCredenciales devuelve la URL base y las credenciales según el modo activo.
func paypalCredenciales(cfg *models.PaypalConfig) (baseURL, clientID, clientSecret string) {
	if strings.EqualFold(cfg.Modo, "live") {
		return paypalBaseLive, cfg.ClientIDProd, cfg.ClientSecretProd
	}
	return paypalBaseSandbox, cfg.ClientIDSandbox, cfg.ClientSecretSandbox
}

var paypalHTTPClient = &http.Client{Timeout: 30 * time.Second}

// ─── Autenticación ────────────────────────────────────────────────────────────

type paypalTokenResp struct {
	AccessToken string `json:"access_token"`
	ExpiresIn   int    `json:"expires_in"`
	Error       string `json:"error"`
	ErrorDesc   string `json:"error_description"`
}

// PaypalAccessToken pide un token OAuth2 con las credenciales de la config activa.
func PaypalAccessToken(cfg *models.PaypalConfig) (string, error) {
	baseURL, clientID, clientSecret := paypalCredenciales(cfg)
	if clientID == "" || clientSecret == "" {
		return "", fmt.Errorf("PayPal: faltan credenciales para el modo '%s'", cfg.Modo)
	}

	form := url.Values{}
	form.Set("grant_type", "client_credentials")

	req, err := http.NewRequest("POST", baseURL+"/v1/oauth2/token", strings.NewReader(form.Encode()))
	if err != nil {
		return "", err
	}
	req.SetBasicAuth(clientID, clientSecret)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "application/json")

	resp, err := paypalHTTPClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("PayPal: no se pudo conectar: %w", err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(io.LimitReader(resp.Body, 256*1024))

	var out paypalTokenResp
	if err := json.Unmarshal(raw, &out); err != nil {
		return "", fmt.Errorf("PayPal: respuesta de token inesperada (%d)", resp.StatusCode)
	}
	if resp.StatusCode < 200 || resp.StatusCode >= 300 || out.AccessToken == "" {
		detalle := out.ErrorDesc
		if detalle == "" {
			detalle = out.Error
		}
		if detalle == "" {
			detalle = string(raw)
		}
		return "", fmt.Errorf("PayPal: autenticación rechazada (%d): %s", resp.StatusCode, detalle)
	}
	return out.AccessToken, nil
}

// paypalRequest hace una llamada autenticada a la API de PayPal.
func paypalRequest(cfg *models.PaypalConfig, token, method, endpoint string, body interface{}) ([]byte, int, error) {
	baseURL, _, _ := paypalCredenciales(cfg)

	var reqBody io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return nil, 0, err
		}
		reqBody = bytes.NewReader(b)
	}

	req, err := http.NewRequest(method, baseURL+endpoint, reqBody)
	if err != nil {
		return nil, 0, err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Accept", "application/json")
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := paypalHTTPClient.Do(req)
	if err != nil {
		return nil, 0, fmt.Errorf("PayPal: error de conexión: %w", err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(io.LimitReader(resp.Body, 512*1024))
	return raw, resp.StatusCode, nil
}

// ─── Creación de orden de pago ────────────────────────────────────────────────

type paypalOrderResp struct {
	ID     string `json:"id"`
	Status string `json:"status"`
	Links  []struct {
		Href string `json:"href"`
		Rel  string `json:"rel"`
	} `json:"links"`
	Message string `json:"message"`
	Details []struct {
		Issue       string `json:"issue"`
		Description string `json:"description"`
	} `json:"details"`
}

// PaypalCrearOrden crea una orden de cobro y devuelve (orderID, urlDeAprobacion).
// La referencia debe ir en formato "contrato-{id}": es lo que permite reconciliar
// después el pago con su contrato, igual que en Bold y dLocal.
func PaypalCrearOrden(cfg *models.PaypalConfig, referencia, descripcion, moneda string, monto float64) (string, string, error) {
	token, err := PaypalAccessToken(cfg)
	if err != nil {
		return "", "", err
	}

	returnURL := cfg.ReturnURL
	if returnURL == "" {
		returnURL = absAppURL("/pago-exitoso?ref=" + referencia)
	}
	cancelURL := cfg.CancelURL
	if cancelURL == "" {
		cancelURL = absAppURL("/pago-cancelado?ref=" + referencia)
	}

	orden := map[string]interface{}{
		"intent": "CAPTURE",
		"purchase_units": []map[string]interface{}{{
			"reference_id": referencia,
			"custom_id":    referencia,
			"description":  truncarPaypal(descripcion, 127),
			"amount": map[string]string{
				"currency_code": moneda,
				"value":         fmt.Sprintf("%.2f", monto),
			},
		}},
		"application_context": map[string]string{
			"return_url":          returnURL,
			"cancel_url":          cancelURL,
			"shipping_preference": "NO_SHIPPING",
			"user_action":         "PAY_NOW",
		},
	}

	raw, status, err := paypalRequest(cfg, token, "POST", "/v2/checkout/orders", orden)
	if err != nil {
		return "", "", err
	}

	var out paypalOrderResp
	if err := json.Unmarshal(raw, &out); err != nil {
		return "", "", fmt.Errorf("PayPal: respuesta inesperada al crear la orden (%d)", status)
	}
	if status < 200 || status >= 300 || out.ID == "" {
		return "", "", fmt.Errorf("PayPal rechazó la orden (%d): %s", status, detallePaypal(out))
	}

	for _, l := range out.Links {
		if l.Rel == "approve" || l.Rel == "payer-action" {
			return out.ID, l.Href, nil
		}
	}
	return out.ID, "", fmt.Errorf("PayPal creó la orden %s pero no devolvió enlace de aprobación", out.ID)
}

// PaypalCapturarOrden cobra una orden ya aprobada por el cliente.
// Devuelve true si quedó capturada (o ya lo estaba).
func PaypalCapturarOrden(cfg *models.PaypalConfig, orderID string) (bool, error) {
	token, err := PaypalAccessToken(cfg)
	if err != nil {
		return false, err
	}

	raw, status, err := paypalRequest(cfg, token, "POST", "/v2/checkout/orders/"+orderID+"/capture", map[string]interface{}{})
	if err != nil {
		return false, err
	}

	var out paypalOrderResp
	_ = json.Unmarshal(raw, &out)

	if out.Status == "COMPLETED" {
		return true, nil
	}
	// PayPal responde 422 ORDER_ALREADY_CAPTURED si ya se cobró: es éxito, no error.
	for _, d := range out.Details {
		if d.Issue == "ORDER_ALREADY_CAPTURED" {
			return true, nil
		}
	}
	if status < 200 || status >= 300 {
		return false, fmt.Errorf("PayPal no pudo capturar la orden (%d): %s", status, detallePaypal(out))
	}
	return false, nil
}

// PaypalConsultarOrden devuelve el estado actual de una orden.
func PaypalConsultarOrden(cfg *models.PaypalConfig, orderID string) (string, error) {
	token, err := PaypalAccessToken(cfg)
	if err != nil {
		return "", err
	}
	raw, status, err := paypalRequest(cfg, token, "GET", "/v2/checkout/orders/"+orderID, nil)
	if err != nil {
		return "", err
	}
	var out paypalOrderResp
	if err := json.Unmarshal(raw, &out); err != nil {
		return "", fmt.Errorf("PayPal: respuesta inesperada (%d)", status)
	}
	if status < 200 || status >= 300 {
		return "", fmt.Errorf("PayPal (%d): %s", status, detallePaypal(out))
	}
	return out.Status, nil
}

// ─── Verificación de webhook ──────────────────────────────────────────────────

// PaypalVerificarWebhook comprueba contra PayPal que la notificación es auténtica.
// Sin esto cualquiera podría enviar un "pago completado" falso.
func PaypalVerificarWebhook(cfg *models.PaypalConfig, headers map[string]string, rawBody []byte) (bool, error) {
	if cfg.WebhookID == "" {
		return false, fmt.Errorf("PayPal: falta configurar el Webhook ID para poder verificar las notificaciones")
	}
	token, err := PaypalAccessToken(cfg)
	if err != nil {
		return false, err
	}

	var evento json.RawMessage = rawBody
	payload := map[string]interface{}{
		"auth_algo":         headers["paypal-auth-algo"],
		"cert_url":          headers["paypal-cert-url"],
		"transmission_id":   headers["paypal-transmission-id"],
		"transmission_sig":  headers["paypal-transmission-sig"],
		"transmission_time": headers["paypal-transmission-time"],
		"webhook_id":        cfg.WebhookID,
		"webhook_event":     evento,
	}

	raw, status, err := paypalRequest(cfg, token, "POST", "/v1/notifications/verify-webhook-signature", payload)
	if err != nil {
		return false, err
	}
	var out struct {
		VerificationStatus string `json:"verification_status"`
	}
	if err := json.Unmarshal(raw, &out); err != nil {
		return false, fmt.Errorf("PayPal: respuesta inesperada al verificar la firma (%d)", status)
	}
	return out.VerificationStatus == "SUCCESS", nil
}

// PaypalEvento es la parte de la notificación que interesa para reconciliar.
type PaypalEvento struct {
	ID         string `json:"id"`
	EventType  string `json:"event_type"`
	ResourceID string
	Referencia string
}

// ParsePaypalWebhook extrae el tipo de evento y la referencia del contrato.
func ParsePaypalWebhook(rawBody []byte) (*PaypalEvento, error) {
	var body struct {
		ID        string `json:"id"`
		EventType string `json:"event_type"`
		Resource  struct {
			ID            string `json:"id"`
			CustomID      string `json:"custom_id"`
			PurchaseUnits []struct {
				ReferenceID string `json:"reference_id"`
				CustomID    string `json:"custom_id"`
			} `json:"purchase_units"`
			SupplementaryData struct {
				RelatedIDs struct {
					OrderID string `json:"order_id"`
				} `json:"related_ids"`
			} `json:"supplementary_data"`
		} `json:"resource"`
	}
	if err := json.Unmarshal(rawBody, &body); err != nil {
		return nil, fmt.Errorf("PayPal: no se pudo interpretar la notificación: %w", err)
	}

	ev := &PaypalEvento{ID: body.ID, EventType: body.EventType, ResourceID: body.Resource.ID}

	// La referencia puede venir en custom_id (capturas) o en purchase_units (órdenes).
	ev.Referencia = body.Resource.CustomID
	if ev.Referencia == "" {
		for _, pu := range body.Resource.PurchaseUnits {
			if pu.CustomID != "" {
				ev.Referencia = pu.CustomID
				break
			}
			if pu.ReferenceID != "" {
				ev.Referencia = pu.ReferenceID
				break
			}
		}
	}
	// En PAYMENT.CAPTURE.* el ID del recurso es la captura, no la orden.
	if body.Resource.SupplementaryData.RelatedIDs.OrderID != "" {
		ev.ResourceID = body.Resource.SupplementaryData.RelatedIDs.OrderID
	}
	return ev, nil
}

// ─── helpers ──────────────────────────────────────────────────────────────────

func detallePaypal(out paypalOrderResp) string {
	if len(out.Details) > 0 {
		d := out.Details[0]
		if d.Description != "" {
			return d.Issue + ": " + d.Description
		}
		return d.Issue
	}
	if out.Message != "" {
		return out.Message
	}
	return "sin detalle"
}

func truncarPaypal(s string, max int) string {
	if len(s) <= max {
		return s
	}
	return s[:max]
}
