package cardissuer

import (
	"context"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"math/big"
	"strings"
	"time"

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

type LocalIssuer struct {
	secret []byte
}

func NewLocalIssuer(secret string) *LocalIssuer {
	return &LocalIssuer{secret: []byte(secret)}
}

func (i *LocalIssuer) CreateVirtualCard(ctx context.Context, params CreateVirtualCardParams) (CreatedVirtualCard, error) {
	_ = ctx

	network := strings.ToLower(strings.TrimSpace(params.Network))
	prefix := "4"
	if network == "mastercard" {
		var err error
		prefix, err = mastercardPrefix()
		if err != nil {
			return CreatedVirtualCard{}, err
		}
	}

	pan, err := generatePAN(prefix, 16)
	if err != nil {
		return CreatedVirtualCard{}, err
	}
	cvv, err := randomDigits(3)
	if err != nil {
		return CreatedVirtualCard{}, err
	}
	externalID, err := security.RandomHex(16)
	if err != nil {
		return CreatedVirtualCard{}, err
	}

	expiry := time.Now().UTC().AddDate(3, 0, 0)
	return CreatedVirtualCard{
		ExternalCardID: "local_card_" + externalID,
		Sensitive: SensitiveCard{
			PAN:      pan,
			CVV:      cvv,
			ExpMonth: int(expiry.Month()),
			ExpYear:  expiry.Year(),
		},
		PANFingerprint: i.fingerprint(pan),
		Last4:          pan[len(pan)-4:],
		ExpMonth:       int(expiry.Month()),
		ExpYear:        expiry.Year(),
	}, nil
}

func (i *LocalIssuer) FreezeVirtualCard(ctx context.Context, externalCardID string) error {
	_ = ctx
	_ = externalCardID
	return nil
}

func (i *LocalIssuer) UnfreezeVirtualCard(ctx context.Context, externalCardID string) error {
	_ = ctx
	_ = externalCardID
	return nil
}

func (i *LocalIssuer) CancelVirtualCard(ctx context.Context, externalCardID string) error {
	_ = ctx
	_ = externalCardID
	return nil
}

func (i *LocalIssuer) SyncSpendingLimit(ctx context.Context, externalCardID string, spendingLimitCents int64) (LimitSyncResult, error) {
	_ = ctx
	_ = externalCardID
	_ = spendingLimitCents

	syncID, err := security.RandomHex(16)
	if err != nil {
		return LimitSyncResult{}, err
	}

	return LimitSyncResult{ExternalSyncID: "local_limit_sync_" + syncID}, nil
}

func (i *LocalIssuer) fingerprint(value string) string {
	mac := hmac.New(sha256.New, i.secret)
	_, _ = mac.Write([]byte(value))
	return hex.EncodeToString(mac.Sum(nil))
}

func generatePAN(prefix string, length int) (string, error) {
	bodyLength := length - len(prefix) - 1
	if bodyLength <= 0 {
		return "", fmt.Errorf("%w: invalid card length", domain.ErrValidation)
	}

	body, err := randomDigits(bodyLength)
	if err != nil {
		return "", err
	}

	withoutCheck := prefix + body
	return withoutCheck + luhnCheckDigit(withoutCheck), nil
}

func mastercardPrefix() (string, error) {
	offset, err := randomInt(5)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("5%d", offset+1), nil
}

func randomDigits(length int) (string, error) {
	out := make([]byte, length)
	for i := range out {
		n, err := randomInt(10)
		if err != nil {
			return "", err
		}
		out[i] = byte('0' + n)
	}
	return string(out), nil
}

func randomInt(max int64) (int64, error) {
	n, err := rand.Int(rand.Reader, big.NewInt(max))
	if err != nil {
		return 0, err
	}
	return n.Int64(), nil
}

func luhnCheckDigit(number string) string {
	sum := 0
	double := true

	for i := len(number) - 1; i >= 0; i-- {
		digit := int(number[i] - '0')
		if double {
			digit *= 2
			if digit > 9 {
				digit -= 9
			}
		}
		sum += digit
		double = !double
	}

	return fmt.Sprintf("%d", (10-(sum%10))%10)
}
