package model

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/tech/sendico/pkg/merrors"
	"go.mongodb.org/mongo-driver/v2/bson"
)

type PaymentType int

const (
	PaymentTypeIban PaymentType = iota
	PaymentTypeCard
	PaymentTypeCardToken
	PaymentTypeBankAccount
	PaymentTypeWallet
	PaymentTypeCryptoAddress
	PaymentTypeLedger
)

var paymentTypeToString = map[PaymentType]string{
	PaymentTypeIban:          "iban",
	PaymentTypeCard:          "card",
	PaymentTypeCardToken:     "cardToken",
	PaymentTypeBankAccount:   "bankAccount",
	PaymentTypeWallet:        "wallet",
	PaymentTypeCryptoAddress: "cryptoAddress",
	PaymentTypeLedger:        "ledger",
}

var paymentTypeFromString = map[string]PaymentType{
	"iban":          PaymentTypeIban,
	"card":          PaymentTypeCard,
	"cardToken":     PaymentTypeCardToken,
	"bankAccount":   PaymentTypeBankAccount,
	"wallet":        PaymentTypeWallet,
	"cryptoAddress": PaymentTypeCryptoAddress,
	"ledger":        PaymentTypeLedger,
}

func (t PaymentType) String() string {
	if v, ok := paymentTypeToString[t]; ok {
		return v
	}
	return "iban"
}

func (t PaymentType) MarshalJSON() ([]byte, error) {
	return json.Marshal(t.String())
}

func (t *PaymentType) UnmarshalJSON(data []byte) error {
	var val string
	if err := json.Unmarshal(data, &val); err != nil {
		return err
	}
	v, ok := paymentTypeFromString[val]
	if !ok {
		return merrors.InvalidArgument(fmt.Sprintf("unknown PaymentType: %q", val))
	}
	*t = v
	return nil
}

type PaymentMethod struct {
	PermissionBound `bson:",inline" json:",inline"`
	Describable     `bson:",inline" json:",inline"`

	RecipientRef bson.ObjectID `bson:"recipientRef" json:"recipientRef"`
	Type         PaymentType   `bson:"type" json:"type"`
	Data         bson.Raw      `bson:"data" json:"data"`
	IsMain       bool          `bson:"isMain" json:"isMain"`
}

type paymentMethodJSON struct {
	PermissionBound `json:",inline"`
	Describable     `json:",inline"`

	RecipientRef bson.ObjectID   `json:"recipientRef"`
	Type         PaymentType     `json:"type"`
	Data         json.RawMessage `json:"data"`
	IsMain       bool            `json:"isMain"`
}

func (m PaymentMethod) MarshalJSON() ([]byte, error) {
	var marshaledData json.RawMessage

	if len(m.Data) > 0 {
		var data bson.M
		if err := bson.Unmarshal(m.Data, &data); err != nil {
			return nil, err
		}
		if data != nil {
			b, err := json.Marshal(data)
			if err != nil {
				return nil, err
			}
			marshaledData = b
		}
	}

	payload := paymentMethodJSON{
		PermissionBound: m.PermissionBound,
		Describable:     m.Describable,
		RecipientRef:    m.RecipientRef,
		Type:            m.Type,
		Data:            marshaledData,
		IsMain:          m.IsMain,
	}

	return json.Marshal(payload)
}

func (m *PaymentMethod) UnmarshalJSON(data []byte) error {
	var payload paymentMethodJSON
	if err := json.Unmarshal(data, &payload); err != nil {
		return err
	}

	m.PermissionBound = payload.PermissionBound
	m.Describable = payload.Describable
	m.RecipientRef = payload.RecipientRef
	m.Type = payload.Type
	m.IsMain = payload.IsMain

	if len(payload.Data) == 0 || bytes.Equal(payload.Data, []byte("null")) {
		m.Data = nil
		return nil
	}

	var rawData map[string]any
	if err := json.Unmarshal(payload.Data, &rawData); err != nil {
		return err
	}

	raw, err := bson.Marshal(rawData)
	if err != nil {
		return err
	}

	m.Data = raw
	return nil
}

type PaymentStatus string

const (
	PaymentStatusUnspecified PaymentStatus = "unspecified"

	// Intent exists, no funds touched yet.
	PaymentStatusCreated PaymentStatus = "created"

	// Internal Sendico operations: holds, ledger, FX, fee calc.
	PaymentStatusAuthorizing PaymentStatus = "authorizing"

	// Funds are outside Sendico (rail/gateway/blockchain/provider).
	// Observe lives ONLY in this state.
	PaymentStatusExecuting PaymentStatus = "executing"

	// Final states.
	PaymentStatusSucceeded PaymentStatus = "success"
	PaymentStatusFailed    PaymentStatus = "failed"
	PaymentStatusCancelled PaymentStatus = "cancelled"
)
