package srequest

import (
	"encoding/json"
	"reflect"
	"strings"
	"testing"

	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

func TestEndpointDTOBuildersAndDecoders(t *testing.T) {
	meta := map[string]string{"note": "meta"}

	t.Run("ledger", func(t *testing.T) {
		payload := LedgerEndpoint{LedgerAccountRef: "acc-1", ContraLedgerAccountRef: "contra-1"}
		endpoint, err := NewLedgerEndpointDTO(payload, meta)
		if err != nil {
			t.Fatalf("build ledger endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeLedger {
			t.Fatalf("expected type %s got %s", EndpointTypeLedger, endpoint.Type)
		}
		if string(endpoint.Data) != `{"ledger_account_ref":"acc-1","contra_ledger_account_ref":"contra-1"}` {
			t.Fatalf("unexpected data: %s", endpoint.Data)
		}
		decoded, err := endpoint.DecodeLedger()
		if err != nil {
			t.Fatalf("decode ledger: %v", err)
		}
		if decoded != payload {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
		meta["note"] = "changed"
		if endpoint.Metadata["note"] != "meta" {
			t.Fatalf("metadata should be copied, got %s", endpoint.Metadata["note"])
		}
	})

	t.Run("managed wallet", func(t *testing.T) {
		payload := ManagedWalletEndpoint{
			ManagedWalletRef: "mw-1",
			Asset: &Asset{
				Chain:           ChainNetworkArbitrumOne,
				TokenSymbol:     "USDC",
				ContractAddress: "0xabc",
			},
		}
		endpoint, err := NewManagedWalletEndpointDTO(payload, nil)
		if err != nil {
			t.Fatalf("build managed wallet endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeManagedWallet {
			t.Fatalf("expected type %s got %s", EndpointTypeManagedWallet, endpoint.Type)
		}
		decoded, err := endpoint.DecodeManagedWallet()
		if err != nil {
			t.Fatalf("decode managed wallet: %v", err)
		}
		if !reflect.DeepEqual(decoded, payload) {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
	})

	t.Run("external chain", func(t *testing.T) {
		payload := ExternalChainEndpoint{
			Asset: &Asset{
				Chain:       ChainNetworkEthereumMainnet,
				TokenSymbol: "ETH",
			},
			Address: "0x123",
			Memo:    "memo",
		}
		endpoint, err := NewExternalChainEndpointDTO(payload, nil)
		if err != nil {
			t.Fatalf("build external chain endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeExternalChain {
			t.Fatalf("expected type %s got %s", EndpointTypeExternalChain, endpoint.Type)
		}
		decoded, err := endpoint.DecodeExternalChain()
		if err != nil {
			t.Fatalf("decode external chain: %v", err)
		}
		if !reflect.DeepEqual(decoded, payload) {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
	})

	t.Run("card", func(t *testing.T) {
		payload := CardEndpoint{
			Pan:       "pan",
			FirstName: "Jane",
			LastName:  "Doe",
			ExpMonth:  12,
			ExpYear:   2030,
			Country:   "US",
		}
		endpoint, err := NewCardEndpointDTO(payload, map[string]string{"k": "v"})
		if err != nil {
			t.Fatalf("build card endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeCard {
			t.Fatalf("expected type %s got %s", EndpointTypeCard, endpoint.Type)
		}
		decoded, err := endpoint.DecodeCard()
		if err != nil {
			t.Fatalf("decode card: %v", err)
		}
		if !reflect.DeepEqual(decoded, payload) {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
		if endpoint.Metadata["k"] != "v" {
			t.Fatalf("expected metadata copy, got %s", endpoint.Metadata["k"])
		}
	})

	t.Run("card token", func(t *testing.T) {
		payload := CardTokenEndpoint{Token: "token", MaskedPan: "****1234"}
		endpoint, err := NewCardTokenEndpointDTO(payload, nil)
		if err != nil {
			t.Fatalf("build card token endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeCardToken {
			t.Fatalf("expected type %s got %s", EndpointTypeCardToken, endpoint.Type)
		}
		decoded, err := endpoint.DecodeCardToken()
		if err != nil {
			t.Fatalf("decode card token: %v", err)
		}
		if !reflect.DeepEqual(decoded, payload) {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
	})

	t.Run("wallet", func(t *testing.T) {
		payload := WalletEndpoint{WalletID: "wallet-1"}
		endpoint, err := NewWalletEndpointDTO(payload, nil)
		if err != nil {
			t.Fatalf("build wallet endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeWallet {
			t.Fatalf("expected type %s got %s", EndpointTypeWallet, endpoint.Type)
		}
		decoded, err := endpoint.DecodeWallet()
		if err != nil {
			t.Fatalf("decode wallet: %v", err)
		}
		if decoded != payload {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
	})

	t.Run("bank account", func(t *testing.T) {
		payload := BankAccountEndpoint{
			RecipientName:        "ACME",
			Inn:                  "inn",
			Kpp:                  "kpp",
			BankName:             "bank",
			Bik:                  "bik",
			AccountNumber:        "123",
			CorrespondentAccount: "456",
		}
		endpoint, err := NewBankAccountEndpointDTO(payload, map[string]string{"note": "n"})
		if err != nil {
			t.Fatalf("build bank account endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeBankAccount {
			t.Fatalf("expected type %s got %s", EndpointTypeBankAccount, endpoint.Type)
		}
		decoded, err := endpoint.DecodeBankAccount()
		if err != nil {
			t.Fatalf("decode bank account: %v", err)
		}
		if !reflect.DeepEqual(decoded, payload) {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
		if endpoint.Metadata["note"] != "n" {
			t.Fatalf("expected metadata copy, got %s", endpoint.Metadata["note"])
		}
	})

	t.Run("iban", func(t *testing.T) {
		payload := IBANEndpoint{
			IBAN:          "DE123",
			AccountHolder: "John Doe",
			BIC:           "BICCODE",
			BankName:      "BankName",
		}
		endpoint, err := NewIBANEndpointDTO(payload, nil)
		if err != nil {
			t.Fatalf("build iban endpoint: %v", err)
		}
		if endpoint.Type != EndpointTypeIBAN {
			t.Fatalf("expected type %s got %s", EndpointTypeIBAN, endpoint.Type)
		}
		decoded, err := endpoint.DecodeIBAN()
		if err != nil {
			t.Fatalf("decode iban: %v", err)
		}
		if !reflect.DeepEqual(decoded, payload) {
			t.Fatalf("decoded payload mismatch: %#v vs %#v", decoded, payload)
		}
	})

	t.Run("type mismatch", func(t *testing.T) {
		endpoint, err := NewLedgerEndpointDTO(LedgerEndpoint{LedgerAccountRef: "acc"}, nil)
		if err != nil {
			t.Fatalf("build ledger endpoint: %v", err)
		}
		if _, err := endpoint.DecodeCard(); err == nil || !strings.Contains(err.Error(), "expected endpoint type") {
			t.Fatalf("expected type mismatch error, got %v", err)
		}
	})

	t.Run("invalid json data", func(t *testing.T) {
		endpoint := Endpoint{Type: EndpointTypeLedger, Data: json.RawMessage("not-json")}
		if _, err := endpoint.DecodeLedger(); err == nil {
			t.Fatalf("expected decode error")
		}
	})

	t.Run("legacy type alias normalizes", func(t *testing.T) {
		raw := []byte(`{"type":"managed_wallet","data":{"managed_wallet_ref":"mw-legacy"}}`)
		var endpoint Endpoint
		if err := json.Unmarshal(raw, &endpoint); err != nil {
			t.Fatalf("unmarshal with legacy type: %v", err)
		}
		if endpoint.Type != EndpointTypeManagedWallet {
			t.Fatalf("expected normalized type %s got %s", EndpointTypeManagedWallet, endpoint.Type)
		}
		payload, err := endpoint.DecodeManagedWallet()
		if err != nil {
			t.Fatalf("decode managed wallet with alias: %v", err)
		}
		if payload.ManagedWalletRef != "mw-legacy" {
			t.Fatalf("decoded payload mismatch from alias: %#v", payload)
		}
	})
}

func TestPaymentIntentJSONRoundTrip(t *testing.T) {
	sourcePayload := LedgerEndpoint{LedgerAccountRef: "source"}
	source, err := NewLedgerEndpointDTO(sourcePayload, map[string]string{"src": "meta"})
	if err != nil {
		t.Fatalf("build source endpoint: %v", err)
	}
	destPayload := ExternalChainEndpoint{Address: "0xabc", Asset: &Asset{Chain: ChainNetworkEthereumMainnet, TokenSymbol: "USDC"}}
	dest, err := NewExternalChainEndpointDTO(destPayload, nil)
	if err != nil {
		t.Fatalf("build destination endpoint: %v", err)
	}

	intent := &PaymentIntent{
		Kind:        PaymentKindPayout,
		Source:      &source,
		Destination: &dest,
		Amount:      &paymenttypes.Money{Amount: "10", Currency: "USD"},
		FX: &FXIntent{
			Pair:              &CurrencyPair{Base: "USD", Quote: "EUR"},
			Side:              FXSideBuyBaseSellQuote,
			Firm:              true,
			TTLms:             5000,
			PreferredProvider: "provider",
			MaxAgeMs:          10,
		},
		SettlementMode: SettlementModeFixReceived,
		Attributes:     map[string]string{"k": "v"},
	}

	data, err := json.Marshal(intent)
	if err != nil {
		t.Fatalf("marshal intent: %v", err)
	}
	var decoded PaymentIntent
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal intent: %v", err)
	}

	if decoded.Kind != intent.Kind || decoded.SettlementMode != intent.SettlementMode {
		t.Fatalf("scalar fields changed after round trip")
	}
	if decoded.Amount == nil || *decoded.Amount != *intent.Amount {
		t.Fatalf("amount mismatch after round trip")
	}
	if decoded.FX == nil || decoded.FX.PreferredProvider != intent.FX.PreferredProvider {
		t.Fatalf("fx mismatch after round trip")
	}
	if decoded.Source == nil || decoded.Destination == nil {
		t.Fatalf("source/destination missing after round trip")
	}
	sourceDecoded, err := decoded.Source.DecodeLedger()
	if err != nil {
		t.Fatalf("decode source after round trip: %v", err)
	}
	if sourceDecoded != sourcePayload {
		t.Fatalf("source payload mismatch after round trip: %#v vs %#v", sourceDecoded, sourcePayload)
	}
	destDecoded, err := decoded.Destination.DecodeExternalChain()
	if err != nil {
		t.Fatalf("decode destination after round trip: %v", err)
	}
	if !reflect.DeepEqual(destDecoded, destPayload) {
		t.Fatalf("destination payload mismatch after round trip: %#v vs %#v", destDecoded, destPayload)
	}
	if decoded.Attributes["k"] != "v" {
		t.Fatalf("attributes mismatch after round trip")
	}
}

func TestPaymentIntentMinimalRoundTrip(t *testing.T) {
	sourcePayload := ManagedWalletEndpoint{ManagedWalletRef: "mw"}
	source, err := NewManagedWalletEndpointDTO(sourcePayload, nil)
	if err != nil {
		t.Fatalf("build source endpoint: %v", err)
	}
	destPayload := LedgerEndpoint{LedgerAccountRef: "dest-ledger"}
	dest, err := NewLedgerEndpointDTO(destPayload, nil)
	if err != nil {
		t.Fatalf("build destination endpoint: %v", err)
	}

	intent := &PaymentIntent{
		Kind:        PaymentKindInternalTransfer,
		Source:      &source,
		Destination: &dest,
		Amount:      &paymenttypes.Money{Amount: "1", Currency: "USD"},
	}

	data, err := json.Marshal(intent)
	if err != nil {
		t.Fatalf("marshal intent: %v", err)
	}
	var decoded PaymentIntent
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("unmarshal intent: %v", err)
	}

	if decoded.Kind != intent.Kind || decoded.FX != nil {
		t.Fatalf("unexpected fx data in minimal intent: %#v", decoded)
	}
	if decoded.Amount == nil || *decoded.Amount != *intent.Amount {
		t.Fatalf("amount mismatch after round trip")
	}
	if decoded.Source == nil || decoded.Destination == nil {
		t.Fatalf("endpoints missing after round trip")
	}
	sourceDecoded, err := decoded.Source.DecodeManagedWallet()
	if err != nil {
		t.Fatalf("decode source: %v", err)
	}
	if !reflect.DeepEqual(sourceDecoded, sourcePayload) {
		t.Fatalf("source payload mismatch: %#v vs %#v", sourceDecoded, sourcePayload)
	}
	destDecoded, err := decoded.Destination.DecodeLedger()
	if err != nil {
		t.Fatalf("decode destination: %v", err)
	}
	if destDecoded != destPayload {
		t.Fatalf("destination payload mismatch: %#v vs %#v", destDecoded, destPayload)
	}
}

func TestLegacyEndpointRoundTrip(t *testing.T) {
	legacy := &LegacyPaymentEndpoint{
		ExternalChain: &ExternalChainEndpoint{
			Asset:   &Asset{Chain: ChainNetworkEthereumMainnet, TokenSymbol: "DAI", ContractAddress: "0xdef"},
			Address: "0x123",
			Memo:    "memo",
		},
		Metadata: map[string]string{"note": "legacy"},
	}

	endpoint, err := LegacyPaymentEndpointToEndpointDTO(legacy)
	if err != nil {
		t.Fatalf("convert legacy to dto: %v", err)
	}
	if endpoint == nil || endpoint.Type != EndpointTypeExternalChain {
		t.Fatalf("unexpected endpoint result: %#v", endpoint)
	}
	legacy.Metadata["note"] = "changed"
	if endpoint.Metadata["note"] != "legacy" {
		t.Fatalf("metadata should be copied from legacy")
	}

	roundTrip, err := EndpointDTOToLegacyPaymentEndpoint(endpoint)
	if err != nil {
		t.Fatalf("convert dto back to legacy: %v", err)
	}
	if roundTrip == nil || roundTrip.ExternalChain == nil {
		t.Fatalf("round trip legacy missing payload: %#v", roundTrip)
	}
	if !reflect.DeepEqual(roundTrip.ExternalChain, legacy.ExternalChain) {
		t.Fatalf("round trip payload mismatch: %#v vs %#v", roundTrip.ExternalChain, legacy.ExternalChain)
	}
	if roundTrip.Metadata["note"] != "legacy" {
		t.Fatalf("metadata mismatch after round trip: %v", roundTrip.Metadata)
	}
}

func TestLegacyEndpointConversionRejectsMultiple(t *testing.T) {
	_, err := LegacyPaymentEndpointToEndpointDTO(&LegacyPaymentEndpoint{
		Ledger: &LedgerEndpoint{LedgerAccountRef: "a"},
		Card:   &CardEndpoint{Pan: "t"},
	})
	if err == nil {
		t.Fatalf("expected error when multiple legacy endpoints are set")
	}
}

func TestEndpointUnmarshalLegacyShape(t *testing.T) {
	raw := []byte(`{"ledger":{"ledger_account_ref":"abc"}}`)
	var endpoint Endpoint
	if err := json.Unmarshal(raw, &endpoint); err != nil {
		t.Fatalf("unmarshal legacy shape: %v", err)
	}
	if endpoint.Type != EndpointTypeLedger {
		t.Fatalf("expected type %s got %s", EndpointTypeLedger, endpoint.Type)
	}
	payload, err := endpoint.DecodeLedger()
	if err != nil {
		t.Fatalf("decode ledger from legacy shape: %v", err)
	}
	if payload.LedgerAccountRef != "abc" {
		t.Fatalf("unexpected payload from legacy shape: %#v", payload)
	}
}
