package client

import (
	"context"
	"testing"

	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
	"google.golang.org/grpc"
)

type stubConnectorClient struct {
	submitReq *connectorv1.SubmitOperationRequest
	getReq    *connectorv1.GetOperationRequest

	submitResp *connectorv1.SubmitOperationResponse
	getResp    *connectorv1.GetOperationResponse
}

func (s *stubConnectorClient) SubmitOperation(_ context.Context, in *connectorv1.SubmitOperationRequest, _ ...grpc.CallOption) (*connectorv1.SubmitOperationResponse, error) {
	s.submitReq = in
	if s.submitResp != nil {
		return s.submitResp, nil
	}
	return &connectorv1.SubmitOperationResponse{}, nil
}

func (s *stubConnectorClient) GetOperation(_ context.Context, in *connectorv1.GetOperationRequest, _ ...grpc.CallOption) (*connectorv1.GetOperationResponse, error) {
	s.getReq = in
	if s.getResp != nil {
		return s.getResp, nil
	}
	return &connectorv1.GetOperationResponse{}, nil
}

func TestCreateCardPayout_UsesOperationRefIdentity(t *testing.T) {
	stub := &stubConnectorClient{
		submitResp: &connectorv1.SubmitOperationResponse{
			Receipt: &connectorv1.OperationReceipt{
				OperationId: "payment-1:hop_4_card_payout_send",
				Status:      connectorv1.OperationStatus_OPERATION_WAITING,
				ProviderRef: "provider-1",
			},
		},
	}
	client := &gatewayClient{client: stub, cfg: Config{}}

	resp, err := client.CreateCardPayout(context.Background(), &mntxv1.CardPayoutRequest{
		PayoutId:       "payout-legacy-id",
		OperationRef:   "payment-1:hop_4_card_payout_send",
		IdempotencyKey: "",
		AmountMinor:    12345,
		Currency:       "RUB",
	})
	if err != nil {
		t.Fatalf("CreateCardPayout returned error: %v", err)
	}
	if resp.GetPayout() == nil {
		t.Fatal("expected payout")
	}
	if got, want := resp.GetPayout().GetPayoutId(), "payment-1:hop_4_card_payout_send"; got != want {
		t.Fatalf("payout_id mismatch: got=%q want=%q", got, want)
	}
	if stub.submitReq == nil || stub.submitReq.GetOperation() == nil {
		t.Fatal("expected submitted operation")
	}
	if got, want := stub.submitReq.GetOperation().GetOperationRef(), "payment-1:hop_4_card_payout_send"; got != want {
		t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := stub.submitReq.GetOperation().GetIdempotencyKey(), "payment-1:hop_4_card_payout_send"; got != want {
		t.Fatalf("idempotency_key mismatch: got=%q want=%q", got, want)
	}
}

func TestGetCardPayoutStatus_UsesPayoutIDAsOperationID(t *testing.T) {
	stub := &stubConnectorClient{
		getResp: &connectorv1.GetOperationResponse{
			Operation: &connectorv1.Operation{
				OperationId: "payment-2:hop_4_card_payout_send",
				Status:      connectorv1.OperationStatus_OPERATION_PROCESSING,
			},
		},
	}
	client := &gatewayClient{client: stub, cfg: Config{}}

	resp, err := client.GetCardPayoutStatus(context.Background(), &mntxv1.GetCardPayoutStatusRequest{
		PayoutId: "payment-2:hop_4_card_payout_send",
	})
	if err != nil {
		t.Fatalf("GetCardPayoutStatus returned error: %v", err)
	}
	if stub.getReq == nil {
		t.Fatal("expected get operation request")
	}
	if got, want := stub.getReq.GetOperationId(), "payment-2:hop_4_card_payout_send"; got != want {
		t.Fatalf("operation_id mismatch: got=%q want=%q", got, want)
	}
	if resp.GetPayout() == nil {
		t.Fatal("expected payout")
	}
	if got, want := resp.GetPayout().GetPayoutId(), "payment-2:hop_4_card_payout_send"; got != want {
		t.Fatalf("payout_id mismatch: got=%q want=%q", got, want)
	}
}

func TestCreateCardTokenPayout_UsesOperationRefWhenReceiptOperationIDMissing(t *testing.T) {
	stub := &stubConnectorClient{
		submitResp: &connectorv1.SubmitOperationResponse{
			Receipt: &connectorv1.OperationReceipt{
				OperationId: "",
				Status:      connectorv1.OperationStatus_OPERATION_PROCESSING,
				ProviderRef: "provider-2",
			},
		},
	}
	client := &gatewayClient{client: stub, cfg: Config{}}

	resp, err := client.CreateCardTokenPayout(context.Background(), &mntxv1.CardTokenPayoutRequest{
		PayoutId:       "legacy-payout-id",
		OperationRef:   "payment-3:hop_4_card_payout_send_2",
		IdempotencyKey: "",
		AmountMinor:    777,
		Currency:       "RUB",
		CardToken:      "tok_123",
	})
	if err != nil {
		t.Fatalf("CreateCardTokenPayout returned error: %v", err)
	}
	if stub.submitReq == nil || stub.submitReq.GetOperation() == nil {
		t.Fatal("expected submitted operation")
	}
	if got, want := stub.submitReq.GetOperation().GetOperationRef(), "payment-3:hop_4_card_payout_send_2"; got != want {
		t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want)
	}
	if got, want := stub.submitReq.GetOperation().GetIdempotencyKey(), "payment-3:hop_4_card_payout_send_2"; got != want {
		t.Fatalf("idempotency_key mismatch: got=%q want=%q", got, want)
	}
	if resp.GetPayout() == nil {
		t.Fatal("expected payout")
	}
	if got, want := resp.GetPayout().GetPayoutId(), "payment-3:hop_4_card_payout_send_2"; got != want {
		t.Fatalf("payout_id mismatch: got=%q want=%q", got, want)
	}
}

func TestCreateCardPayout_SendsCommentAsSeparateParam(t *testing.T) {
	stub := &stubConnectorClient{
		submitResp: &connectorv1.SubmitOperationResponse{
			Receipt: &connectorv1.OperationReceipt{
				OperationId: "payment-4:hop_4_card_payout_send",
				Status:      connectorv1.OperationStatus_OPERATION_WAITING,
			},
		},
	}
	client := &gatewayClient{client: stub, cfg: Config{}}

	_, err := client.CreateCardPayout(context.Background(), &mntxv1.CardPayoutRequest{
		OperationRef:   "payment-4:hop_4_card_payout_send",
		IdempotencyKey: "idem-4",
		AmountMinor:    1000,
		Currency:       "RUB",
		Comment:        "invoice-44",
		Metadata: map[string]string{
			"comment":  "legacy-comment",
			"trace_id": "trace-44",
		},
	})
	if err != nil {
		t.Fatalf("CreateCardPayout returned error: %v", err)
	}
	if stub.submitReq == nil || stub.submitReq.GetOperation() == nil {
		t.Fatal("expected submitted operation")
	}

	fields := stub.submitReq.GetOperation().GetParams().GetFields()
	if got, want := fields["comment"].GetStringValue(), "invoice-44"; got != want {
		t.Fatalf("comment param mismatch: got=%q want=%q", got, want)
	}
	metadataField, ok := fields["metadata"]
	if !ok || metadataField == nil || metadataField.GetStructValue() == nil {
		t.Fatal("expected metadata param")
	}
	metadata := metadataField.GetStructValue().GetFields()
	if _, hasComment := metadata["comment"]; hasComment {
		t.Fatalf("metadata must not include comment key")
	}
	if got, want := metadata["trace_id"].GetStringValue(), "trace-44"; got != want {
		t.Fatalf("trace_id metadata mismatch: got=%q want=%q", got, want)
	}
}
