package monetix

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strings"
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"go.uber.org/zap"
)

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
	return f(r)
}

func mustMarshalJSON(t *testing.T, value any) []byte {
	t.Helper()
	payload, err := json.Marshal(value)
	if err != nil {
		t.Fatalf("json marshal failed: %v", err)
	}
	return payload
}

func TestSendCardPayout_SignsPayload(t *testing.T) {
	secret := "secret"
	var captured CardPayoutRequest

	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			if r.URL.Path != "/v2/payment/card/payout" {
				t.Fatalf("expected payout path, got %q", r.URL.Path)
			}
			body, _ := io.ReadAll(r.Body)
			if err := json.Unmarshal(body, &captured); err != nil {
				t.Fatalf("failed to decode request: %v", err)
			}
			resp := APIResponse{}
			resp.Operation.RequestID = "req-1"
			payload := mustMarshalJSON(t, resp)
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader(payload)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: secret,
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	result, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !result.Accepted {
		t.Fatalf("expected accepted response")
	}
	if captured.General.Signature == "" {
		t.Fatalf("expected signature in request")
	}

	signed := captured
	signed.General.Signature = ""
	expectedSig, err := SignPayload(signed, secret)
	if err != nil {
		t.Fatalf("failed to compute signature: %v", err)
	}
	if captured.General.Signature != expectedSig {
		t.Fatalf("expected signature %q, got %q", expectedSig, captured.General.Signature)
	}
}

func TestSendCardPayout_NormalizesTwoDigitYearBeforeSend(t *testing.T) {
	var captured CardPayoutRequest

	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			body, _ := io.ReadAll(r.Body)
			if err := json.Unmarshal(body, &captured); err != nil {
				t.Fatalf("failed to decode request: %v", err)
			}
			payload := mustMarshalJSON(t, APIResponse{})
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader(payload)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 30, Month: 12, CardHolder: "JANE DOE"},
	}

	_, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if captured.Card.Year != 2030 {
		t.Fatalf("expected normalized year 2030, got %d", captured.Card.Year)
	}
}

func TestSendCardPayout_HTTPError(t *testing.T) {
	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			body := `{"code":"E100","message":"denied"}`
			return &http.Response{
				StatusCode: http.StatusBadRequest,
				Body:       io.NopCloser(strings.NewReader(body)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	result, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if result.Accepted {
		t.Fatalf("expected rejected response")
	}
	if result.ErrorCode != "E100" {
		t.Fatalf("expected error code E100, got %q", result.ErrorCode)
	}
	if result.ErrorMessage != "denied" {
		t.Fatalf("expected error message denied, got %q", result.ErrorMessage)
	}
}

func TestSendCardPayout_HTTPAcceptedBodyErrorStillAccepted(t *testing.T) {
	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			body := `{"status":"error","code":"3062","message":"Payment details not received"}`
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(strings.NewReader(body)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	result, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !result.Accepted {
		t.Fatalf("expected accepted response")
	}
	if result.ProviderStatus != "error" {
		t.Fatalf("expected provider status error, got %q", result.ProviderStatus)
	}
	if result.ErrorCode != "3062" {
		t.Fatalf("expected error code %q, got %q", "3062", result.ErrorCode)
	}
	if result.ErrorMessage != "Payment details not received" {
		t.Fatalf("expected error message, got %q", result.ErrorMessage)
	}
}

func TestSendCardPayout_HTTPErrorFallsBackToOperationCode(t *testing.T) {
	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			body := `{"operation":{"code":"3061","message":"Transaction not found"}}`
			return &http.Response{
				StatusCode: http.StatusBadRequest,
				Body:       io.NopCloser(strings.NewReader(body)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	result, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if result.Accepted {
		t.Fatalf("expected rejected response")
	}
	if result.ErrorCode != "3061" {
		t.Fatalf("expected error code %q, got %q", "3061", result.ErrorCode)
	}
	if result.ErrorMessage != "Transaction not found" {
		t.Fatalf("expected error message, got %q", result.ErrorMessage)
	}
}

type errorReadCloser struct {
	err error
}

func (e errorReadCloser) Read(_ []byte) (int, error) {
	return 0, e.err
}

func (e errorReadCloser) Close() error {
	return nil
}

type countingReadCloser struct {
	data      []byte
	offset    int
	readBytes int
	chunkSize int
}

func (c *countingReadCloser) Read(p []byte) (int, error) {
	if c.offset >= len(c.data) {
		return 0, io.EOF
	}
	n := c.chunkSize
	if n <= 0 || n > len(p) {
		n = len(p)
	}
	remaining := len(c.data) - c.offset
	if n > remaining {
		n = remaining
	}
	copy(p[:n], c.data[c.offset:c.offset+n])
	c.offset += n
	c.readBytes += n
	return n, nil
}

func (c *countingReadCloser) Close() error {
	return nil
}

func counterValue(t *testing.T, counter prometheus.Counter) float64 {
	t.Helper()
	metric := &dto.Metric{}
	if err := counter.Write(metric); err != nil {
		t.Fatalf("failed to read counter value: %v", err)
	}
	return metric.GetCounter().GetValue()
}

func TestSendCardPayout_ReadBodyError(t *testing.T) {
	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       errorReadCloser{err: errors.New("read failed")},
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	_, err := client.CreateCardPayout(context.Background(), req)
	if err == nil {
		t.Fatalf("expected read error")
	}
	if !strings.Contains(err.Error(), "failed to read monetix response") {
		t.Fatalf("expected wrapped read error, got %v", err)
	}
}

func TestSendCardPayout_DecodeErrorTrackedAsHTTPError(t *testing.T) {
	initMetrics()
	beforeAccepted := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeAccepted))
	beforeHTTPError := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeHTTPError))

	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(strings.NewReader("{")),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	_, err := client.CreateCardPayout(context.Background(), req)
	if err == nil {
		t.Fatalf("expected decode error")
	}
	if !strings.Contains(err.Error(), "failed to decode monetix response") {
		t.Fatalf("expected wrapped decode error, got %v", err)
	}

	afterAccepted := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeAccepted))
	afterHTTPError := counterValue(t, cardPayoutRequests.WithLabelValues(outcomeHTTPError))

	if afterAccepted != beforeAccepted {
		t.Fatalf("accepted counter changed unexpectedly: before=%v after=%v", beforeAccepted, afterAccepted)
	}
	if afterHTTPError != beforeHTTPError+1 {
		t.Fatalf("http_error counter not incremented: before=%v after=%v", beforeHTTPError, afterHTTPError)
	}
}

func TestSendCardPayout_ReadsFullMonetixRegistrationBody(t *testing.T) {
	rawBody := `{"status":"waiting","request_id":"req-123","project_id":10,"payment_id":"pay-1"}`
	body := &countingReadCloser{
		data:      []byte(rawBody),
		chunkSize: 7,
	}

	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       body,
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	result, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !result.Accepted {
		t.Fatalf("expected accepted response")
	}
	if result.ProviderRequestID != "req-123" {
		t.Fatalf("expected provider request id req-123, got %q", result.ProviderRequestID)
	}
	if result.ProviderStatus != "waiting" {
		t.Fatalf("expected provider status waiting, got %q", result.ProviderStatus)
	}
	if body.readBytes != len(rawBody) {
		t.Fatalf("expected full body read (%d), got %d", len(rawBody), body.readBytes)
	}
}

func TestSendCardPayout_ProviderStatusFallsBackToOperationStatus(t *testing.T) {
	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			body := `{"operation":{"request_id":"req-1","status":"processing"}}`
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(strings.NewReader(body)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardPayoutRequest{
		General: General{ProjectID: 1, PaymentID: "payout-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Payment: Payment{Amount: 1000, Currency: "RUB"},
		Card:    Card{PAN: "4111111111111111", Year: 2030, Month: 12, CardHolder: "JANE DOE"},
	}

	result, err := client.CreateCardPayout(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if result.ProviderStatus != "processing" {
		t.Fatalf("expected provider status processing, got %q", result.ProviderStatus)
	}
}

func TestSendCardTokenization_NormalizesTwoDigitYearBeforeSend(t *testing.T) {
	var captured CardTokenizeRequest

	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			if r.URL.Path != "/v1/tokenize" {
				t.Fatalf("expected tokenization path, got %q", r.URL.Path)
			}
			body, _ := io.ReadAll(r.Body)
			if err := json.Unmarshal(body, &captured); err != nil {
				t.Fatalf("failed to decode request: %v", err)
			}
			payload := mustMarshalJSON(t, APIResponse{})
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader(payload)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	cfg := Config{
		BaseURL:   "https://monetix.test",
		SecretKey: "secret",
	}
	client := NewClient(cfg, httpClient, zap.NewNop())

	req := CardTokenizeRequest{
		General: General{ProjectID: 1, PaymentID: "tokenize-1"},
		Customer: Customer{
			ID:        "cust-1",
			FirstName: "Jane",
			LastName:  "Doe",
			IP:        "203.0.113.10",
		},
		Card: CardTokenize{
			PAN:        "4111111111111111",
			Year:       30,
			Month:      12,
			CardHolder: "JANE DOE",
			CVV:        "123",
		},
	}

	_, err := client.CreateCardTokenization(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if captured.Card.Year != 2030 {
		t.Fatalf("expected normalized year 2030, got %d", captured.Card.Year)
	}
}
