package gateway

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/tech/sendico/pkg/merrors"
)

const (
	minCardPANLength = 12
	maxCardPANLength = 19
)

func normalizeCardNumber(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return ""
	}
	var b strings.Builder
	b.Grow(len(value))
	for _, r := range value {
		if r >= '0' && r <= '9' {
			b.WriteRune(r)
		}
	}
	return b.String()
}

func validateCardPAN(pan string, field string) error {
	normalized, err := normalizedPANForValidation(pan, field)
	if err != nil {
		return err
	}
	if !isValidPANLuhn(normalized) {
		return merrors.InvalidArgument("card_pan checksum is invalid", field)
	}
	return nil
}

func normalizedPANForValidation(pan string, field string) (string, error) {
	field = strings.TrimSpace(field)
	if field == "" {
		field = "card_pan"
	}

	clean := strings.TrimSpace(pan)
	if clean == "" {
		return "", merrors.InvalidArgument("card_pan is required", field)
	}

	compact := strings.NewReplacer(" ", "", "-", "").Replace(clean)
	if compact == "" {
		return "", merrors.InvalidArgument("card_pan must contain only digits (spaces/hyphens allowed)", field)
	}

	normalized := normalizeCardNumber(compact)
	if normalized != compact {
		return "", merrors.InvalidArgument("card_pan must contain only digits (spaces/hyphens allowed)", field)
	}
	if len(normalized) < minCardPANLength || len(normalized) > maxCardPANLength {
		return "", merrors.InvalidArgument(
			fmt.Sprintf("card_pan length must be between %d and %d", minCardPANLength, maxCardPANLength),
			field,
		)
	}
	return normalized, nil
}

func isValidPANLuhn(number string) bool {
	if len(number) < minCardPANLength || len(number) > maxCardPANLength {
		return false
	}

	sum := 0
	double := false
	for i := len(number) - 1; i >= 0; i-- {
		d := int(number[i] - '0')
		if d < 0 || d > 9 {
			return false
		}
		if double {
			d *= 2
			if d > 9 {
				d -= 9
			}
		}
		sum += d
		double = !double
	}
	return sum%10 == 0
}

func generatePANWithLuhn(prefix string, sequence uint64, panLength int) (string, error) {
	prefix = normalizeCardNumber(prefix)
	if prefix == "" {
		return "", merrors.InvalidArgument("prefix is required", "prefix")
	}
	if panLength < minCardPANLength || panLength > maxCardPANLength {
		return "", merrors.InvalidArgument(
			fmt.Sprintf("pan_length must be between %d and %d", minCardPANLength, maxCardPANLength),
			"pan_length",
		)
	}

	bodyLen := panLength - 1
	if len(prefix) > bodyLen {
		return "", merrors.InvalidArgument("prefix is too long for selected pan_length", "prefix")
	}

	seqWidth := bodyLen - len(prefix)
	seqMod := pow10(seqWidth)
	body := prefix + fmt.Sprintf("%0*d", seqWidth, sequence%seqMod)

	for checksum := 0; checksum <= 9; checksum++ {
		candidate := body + strconv.Itoa(checksum)
		if isValidPANLuhn(candidate) {
			return candidate, nil
		}
	}
	return "", merrors.Internal("failed to generate PAN checksum digit")
}

func generatePANSeriesWithLuhn(prefix string, count int, panLength int) ([]string, error) {
	if count <= 0 {
		return nil, nil
	}
	out := make([]string, 0, count)
	for i := 0; i < count; i++ {
		pan, err := generatePANWithLuhn(prefix, uint64(i), panLength)
		if err != nil {
			return nil, err
		}
		out = append(out, pan)
	}
	return out, nil
}

func pow10(n int) uint64 {
	if n <= 0 {
		return 1
	}
	p := uint64(1)
	for i := 0; i < n; i++ {
		p *= 10
	}
	return p
}
