package gateway

import (
	"strings"
	"time"

	"github.com/tech/sendico/gateway/mntx/internal/service/monetix"
	"github.com/tech/sendico/pkg/merrors"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
)

type tokenizeCardInput struct {
	pan    string
	month  uint32
	year   uint32
	holder string
	cvv    string
}

func validateCardTokenizeRequest(req *mntxv1.CardTokenizeRequest, cfg monetix.Config) (*tokenizeCardInput, error) {
	if req == nil {
		return nil, newPayoutError("invalid_request", merrors.InvalidArgument("request cannot be empty"))
	}
	if strings.TrimSpace(req.GetRequestId()) == "" {
		return nil, newPayoutError("missing_request_id", merrors.InvalidArgument("request_id is required", "request_id"))
	}
	if strings.TrimSpace(req.GetCustomerId()) == "" {
		return nil, newPayoutError("missing_customer_id", merrors.InvalidArgument("customer_id is required", "customer_id"))
	}
	if strings.TrimSpace(req.GetCustomerFirstName()) == "" {
		return nil, newPayoutError("missing_customer_first_name", merrors.InvalidArgument("customer_first_name is required", "customer_first_name"))
	}
	if strings.TrimSpace(req.GetCustomerLastName()) == "" {
		return nil, newPayoutError("missing_customer_last_name", merrors.InvalidArgument("customer_last_name is required", "customer_last_name"))
	}
	if strings.TrimSpace(req.GetCustomerIp()) == "" {
		return nil, newPayoutError("missing_customer_ip", merrors.InvalidArgument("customer_ip is required", "customer_ip"))
	}

	card := extractTokenizeCard(req)
	if card.pan == "" {
		return nil, newPayoutError("missing_card_pan", merrors.InvalidArgument("card_pan is required", "card.pan"))
	}
	if card.holder == "" {
		return nil, newPayoutError("missing_card_holder", merrors.InvalidArgument("card_holder is required", "card.holder"))
	}
	if card.month == 0 || card.month > 12 {
		return nil, newPayoutError("invalid_expiry_month", merrors.InvalidArgument("card_exp_month must be between 1 and 12", "card.exp_month"))
	}
	if card.year == 0 {
		return nil, newPayoutError("invalid_expiry_year", merrors.InvalidArgument("card_exp_year must be provided", "card.exp_year"))
	}
	if card.cvv == "" {
		return nil, newPayoutError("missing_cvv", merrors.InvalidArgument("card_cvv is required", "card.cvv"))
	}
	if expired(card.month, card.year) {
		return nil, newPayoutError("expired_card", merrors.InvalidArgument("card expiry is in the past", "card.expiry"))
	}

	if cfg.RequireCustomerAddress {
		if strings.TrimSpace(req.GetCustomerCountry()) == "" {
			return nil, newPayoutError("missing_customer_country", merrors.InvalidArgument("customer_country is required", "customer_country"))
		}
		if strings.TrimSpace(req.GetCustomerCity()) == "" {
			return nil, newPayoutError("missing_customer_city", merrors.InvalidArgument("customer_city is required", "customer_city"))
		}
		if strings.TrimSpace(req.GetCustomerAddress()) == "" {
			return nil, newPayoutError("missing_customer_address", merrors.InvalidArgument("customer_address is required", "customer_address"))
		}
		if strings.TrimSpace(req.GetCustomerZip()) == "" {
			return nil, newPayoutError("missing_customer_zip", merrors.InvalidArgument("customer_zip is required", "customer_zip"))
		}
	}

	return card, nil
}

func extractTokenizeCard(req *mntxv1.CardTokenizeRequest) *tokenizeCardInput {
	card := req.GetCard()
	if card != nil {
		return &tokenizeCardInput{
			pan:    strings.TrimSpace(card.GetPan()),
			month:  card.GetExpMonth(),
			year:   card.GetExpYear(),
			holder: strings.TrimSpace(card.GetCardHolder()),
			cvv:    strings.TrimSpace(card.GetCvv()),
		}
	}
	return &tokenizeCardInput{
		pan:    strings.TrimSpace(req.GetCardPan()),
		month:  req.GetCardExpMonth(),
		year:   req.GetCardExpYear(),
		holder: strings.TrimSpace(req.GetCardHolder()),
		cvv:    strings.TrimSpace(req.GetCardCvv()),
	}
}

func expired(month uint32, year uint32) bool {
	now := time.Now()
	y := int(year)
	m := time.Month(month)
	// Normalize 2-digit years: assume 2000-2099.
	if y < 100 {
		y += 2000
	}
	expiry := time.Date(y, m, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 1, -1)
	return now.After(expiry)
}
