package bankledger

import (
	"fmt"
	"strings"

	"github.com/niels/banking-app/backend/internal/domain"
)

const (
	DefaultBankCode = "SWFN"
	DefaultBIC      = "SWFNNL2A"
)

func GenerateDutchIBAN(accountNumber string) (string, error) {
	digits := onlyDigits(accountNumber)
	if digits == "" {
		return "", fmt.Errorf("%w: account_number must contain digits for IBAN generation", domain.ErrValidation)
	}
	if len(digits) > 10 {
		digits = digits[len(digits)-10:]
	}
	if len(digits) < 10 {
		digits = strings.Repeat("0", 10-len(digits)) + digits
	}

	bban := DefaultBankCode + digits
	checkDigits, err := ibanCheckDigits("NL", bban)
	if err != nil {
		return "", err
	}

	iban := fmt.Sprintf("NL%02d%s", checkDigits, bban)
	if err := ValidateIBAN(iban); err != nil {
		return "", err
	}
	return iban, nil
}

func NormalizeIBAN(raw string) string {
	return strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(raw), " ", ""))
}

func ValidateIBAN(raw string) error {
	iban := NormalizeIBAN(raw)
	if len(iban) < 15 || len(iban) > 34 {
		return fmt.Errorf("%w: iban must be between 15 and 34 characters", domain.ErrValidation)
	}
	for _, r := range iban {
		if !isAlnum(r) {
			return fmt.Errorf("%w: iban must contain only letters and digits", domain.ErrValidation)
		}
	}

	mod, err := ibanMod97(iban[4:] + iban[:4])
	if err != nil {
		return err
	}
	if mod != 1 {
		return fmt.Errorf("%w: iban checksum is invalid", domain.ErrValidation)
	}
	return nil
}

func NormalizeBIC(raw string) string {
	return strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(raw), " ", ""))
}

func ValidateBIC(raw string) error {
	bic := NormalizeBIC(raw)
	if len(bic) != 8 && len(bic) != 11 {
		return fmt.Errorf("%w: bic must be 8 or 11 characters", domain.ErrValidation)
	}
	for i, r := range bic {
		switch {
		case i < 4 && !isUpperLetter(r):
			return fmt.Errorf("%w: bic bank code must contain letters", domain.ErrValidation)
		case i >= 4 && i < 6 && !isUpperLetter(r):
			return fmt.Errorf("%w: bic country code must contain letters", domain.ErrValidation)
		case i >= 6 && !isAlnum(r):
			return fmt.Errorf("%w: bic location and branch code must contain letters or digits", domain.ErrValidation)
		}
	}
	return nil
}

func ibanCheckDigits(country, bban string) (int, error) {
	mod, err := ibanMod97(strings.ToUpper(bban + country + "00"))
	if err != nil {
		return 0, err
	}
	return 98 - mod, nil
}

func ibanMod97(value string) (int, error) {
	mod := 0
	for _, r := range value {
		switch {
		case r >= '0' && r <= '9':
			mod = (mod*10 + int(r-'0')) % 97
		case r >= 'A' && r <= 'Z':
			n := int(r-'A') + 10
			mod = (mod*10 + n/10) % 97
			mod = (mod*10 + n%10) % 97
		default:
			return 0, fmt.Errorf("%w: iban contains invalid characters", domain.ErrValidation)
		}
	}
	return mod, nil
}

func onlyDigits(value string) string {
	var builder strings.Builder
	for _, r := range value {
		if r >= '0' && r <= '9' {
			builder.WriteRune(r)
		}
	}
	return builder.String()
}

func isAlnum(r rune) bool {
	return (r >= '0' && r <= '9') || isUpperLetter(r)
}

func isUpperLetter(r rune) bool {
	return r >= 'A' && r <= 'Z'
}
