package services

import (
	"context"
	"crypto/hmac"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/midtrans/midtrans-go"
	"github.com/midtrans/midtrans-go/snap"
	"ticketing-backend/internal/config"
	"ticketing-backend/internal/models"
)

type PaymentService struct {
	pool         *pgxpool.Pool
	serverKey    string
	clientKey    string
	isProduction bool
	settings     *config.AppSettings
}

func NewPaymentService(pool *pgxpool.Pool, serverKey, clientKey string, isProduction bool, settings *config.AppSettings) *PaymentService {
	return &PaymentService{
		pool:         pool,
		serverKey:    serverKey,
		clientKey:    clientKey,
		isProduction: isProduction,
		settings:     settings,
	}
}

var defaultPaymentMethods = []string{
	"qris", "gopay", "shopeepay",
	"bank_transfer", "credit_card",
}

func (s *PaymentService) getEnabledMethods() []string {
	methods := s.settings.GetPaymentMethods()
	if len(methods) == 0 {
		return defaultPaymentMethods
	}
	allowed := map[string]bool{
		"qris": true, "gopay": true,
		"shopeepay": true, "bank_transfer": true,
		"credit_card": true,
	}
	var filtered []string
	for _, m := range methods {
		if allowed[m] {
			filtered = append(filtered, m)
		}
	}
	if len(filtered) == 0 {
		return defaultPaymentMethods
	}
	return filtered
}

func (s *PaymentService) isGatewayEnabled() bool {
	return s.settings.IsPaymentGatewayEnabled()
}

func (s *PaymentService) CreatePayment(orderID string, amount float64, buyerEmail, buyerName string) (*models.Payment, error) {
	ctx := context.Background()

	if !s.isGatewayEnabled() {
		if amount <= 0 {
			return s.createFreePayment(ctx, orderID, buyerEmail, buyerName)
		}
		return nil, fmt.Errorf("payment gateway is disabled")
	}

	paymentAmount := math.Max(1000, math.Round(amount))
	enabledMethods := s.getEnabledMethods()

	var snapClient snap.Client
	if s.isProduction {
		snapClient.New(s.serverKey, midtrans.Production)
	} else {
		snapClient.New(s.serverKey, midtrans.Sandbox)
	}

	req := &snap.Request{
		TransactionDetails: midtrans.TransactionDetails{
			OrderID:  orderID,
			GrossAmt: int64(paymentAmount),
		},
		CustomerDetail: &midtrans.CustomerDetails{
			FName: buyerName,
			Email: buyerEmail,
		},
		Items: &[]midtrans.ItemDetails{
			{
				ID:    orderID,
				Price: int64(paymentAmount),
				Qty:   1,
				Name:  "Event Ticket",
			},
		},
	}

	if len(enabledMethods) > 0 {
		ep := make([]snap.SnapPaymentType, len(enabledMethods))
		for i, m := range enabledMethods {
			ep[i] = snap.SnapPaymentType(m)
		}
		req.EnabledPayments = ep
	}

	resp, midErr := snapClient.CreateTransaction(req)
	if midErr != nil {
		return nil, fmt.Errorf("midtrans transaction creation failed: %s", midErr.Message)
	}

	expiresAt := time.Now().Add(10 * time.Minute)

	var payment models.Payment
	paymentID := models.NewUUID()
	var snapToken *string
	if resp.Token != "" {
		snapToken = &resp.Token
	}
	var paymentURL *string

	transactionID := orderID

	var scanErr error
	scanErr = s.pool.QueryRow(ctx,
		`INSERT INTO payments (id, order_id, amount, gateway, gateway_transaction_id,
			snap_token, payment_url, expires_at, status)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		 RETURNING id, order_id, amount, gateway, gateway_transaction_id, snap_token,
		           payment_url, expires_at, status, created_at, updated_at`,
		paymentID, orderID, paymentAmount, "midtrans", transactionID,
		snapToken, paymentURL, expiresAt, "pending",
	).Scan(&payment.ID, &payment.OrderID, &payment.Amount, &payment.Gateway,
		&payment.GatewayTransactionID, &payment.SnapToken, &payment.PaymentURL,
		&payment.ExpiresAt, &payment.Status, &payment.CreatedAt, &payment.UpdatedAt)

	if scanErr != nil {
		return nil, fmt.Errorf("failed to create payment record: %w", scanErr)
	}

	return &payment, nil
}

func (s *PaymentService) createFreePayment(ctx context.Context, orderID, buyerEmail, buyerName string) (*models.Payment, error) {
	paymentID := models.NewUUID()
	var payment models.Payment
	err := s.pool.QueryRow(ctx,
		`INSERT INTO payments (id, order_id, amount, gateway, status)
		 VALUES ($1, $2, 0, 'free', 'completed')
		 RETURNING id, order_id, amount, gateway, status, created_at, updated_at`,
		paymentID, orderID,
	).Scan(&payment.ID, &payment.OrderID, &payment.Amount, &payment.Gateway,
		&payment.Status, &payment.CreatedAt, &payment.UpdatedAt)

	if err != nil {
		return nil, fmt.Errorf("failed to create free payment: %w", err)
	}
	return &payment, nil
}

func (s *PaymentService) GetPaymentStatus(paymentID string) (*models.Payment, error) {
	ctx := context.Background()
	var payment models.Payment
	err := s.pool.QueryRow(ctx,
		`SELECT id, order_id, amount, gateway, gateway_transaction_id, qris_code,
		        snap_token, payment_url, expires_at, status, created_at, updated_at
		 FROM payments WHERE id = $1`, paymentID,
	).Scan(&payment.ID, &payment.OrderID, &payment.Amount, &payment.Gateway,
		&payment.GatewayTransactionID, &payment.QRISCode, &payment.SnapToken,
		&payment.PaymentURL, &payment.ExpiresAt, &payment.Status,
		&payment.CreatedAt, &payment.UpdatedAt)

	if err != nil {
		return nil, fmt.Errorf("payment not found")
	}
	return &payment, nil
}

func (s *PaymentService) CompletePayment(paymentID, transactionID string) error {
	_, err := s.pool.Exec(context.Background(),
		`UPDATE payments SET status = 'completed', gateway_transaction_id = COALESCE(NULLIF($2, ''), gateway_transaction_id),
		 updated_at = NOW() WHERE id = $1`,
		paymentID, transactionID,
	)
	return err
}

func (s *PaymentService) FailPayment(paymentID, reason string) error {
	_, err := s.pool.Exec(context.Background(),
		`UPDATE payments SET status = 'failed',
		 webhook_data = jsonb_set(COALESCE(webhook_data, '{}'::jsonb), '{failure_reason}', $2),
		 updated_at = NOW() WHERE id = $1`,
		paymentID, fmt.Sprintf(`"%s"`, reason),
	)
	return err
}

func (s *PaymentService) VerifyWebhookSignature(payload []byte, signature string) bool {
	var data map[string]interface{}
	if err := json.Unmarshal(payload, &data); err != nil {
		return false
	}

	orderID, _ := data["order_id"].(string)
	statusCode, _ := data["status_code"].(string)
	grossAmount, _ := data["gross_amount"].(string)
	sigKey, _ := data["signature_key"].(string)

	if signature == "" {
		signature = sigKey
	}

	hash := hmac.New(sha512.New, []byte(s.serverKey))
	hash.Write([]byte(orderID + statusCode + grossAmount))
	computed := hex.EncodeToString(hash.Sum(nil))

	return hmac.Equal([]byte(computed), []byte(signature))
}

type MidtransNotification struct {
	TransactionID     string `json:"transaction_id"`
	OrderID           string `json:"order_id"`
	TransactionStatus string `json:"transaction_status"`
	FraudStatus       string `json:"fraud_status"`
	PaymentType       string `json:"payment_type"`
	GrossAmount       string `json:"gross_amount"`
	TransactionTime   string `json:"transaction_time"`
	SettlementTime    string `json:"settlement_time"`
}

func (s *PaymentService) HandleWebhook(notif *MidtransNotification) (string, error) {
	ctx := context.Background()

	status := mapTransactionStatus(notif.TransactionStatus, notif.FraudStatus)

	details, _ := json.Marshal(notif)
	_, _ = s.pool.Exec(ctx,
		`INSERT INTO system_logs (log_type, action, resource_type, resource_id, status, details)
		 VALUES ('payment', 'webhook_received', 'payment', $1, $2, $3)`,
		notif.OrderID, status, string(details),
	)

	return status, nil
}

func mapTransactionStatus(transactionStatus, fraudStatus string) string {
	ts := fmt.Sprintf("%v", transactionStatus)
	fs := fmt.Sprintf("%v", fraudStatus)

	switch ts {
	case "settlement", "capture":
		if ts == "capture" && fs != "accept" {
			return "pending"
		}
		return "completed"
	case "expire", "expired", "deny", "cancel":
		return "expired"
	case "pending":
		return "pending"
	default:
		return "pending"
	}
}
