package cbr //nolint:testpackage

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"testing"

	"github.com/tech/sendico/pkg/merrors"
	"go.uber.org/zap"
)

func TestFetchTickerDaily(t *testing.T) {
	t.Parallel()

	transport := &stubRoundTripper{
		responses: map[string]stubResponse{
			"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
			"/scripts/XML_daily.asp":   {body: dailyRatesXML},
		},
	}

	conn, err := NewConnector(zap.NewNop(), map[string]any{
		"base_url":                "http://cbr.test",
		"http_round_tripper":      transport,
		"request_timeout_seconds": 1,
	})
	if err != nil {
		t.Fatalf("NewConnector returned error: %v", err)
	}

	ticker, err := conn.FetchTicker(context.Background(), "USD")
	if err != nil {
		t.Fatalf("FetchTicker returned error: %v", err)
	}

	if ticker.Provider != "cbr" {
		t.Fatalf("unexpected provider: %s", ticker.Provider)
	}
	if ticker.BidPrice != "95.12340000" || ticker.AskPrice != "95.12340000" {
		t.Fatalf("unexpected bid/ask: %s / %s", ticker.BidPrice, ticker.AskPrice)
	}
	if ticker.Symbol != "USD" {
		t.Fatalf("unexpected symbol: %s", ticker.Symbol)
	}
}

func TestFetchTickerValidatesDailyEntry(t *testing.T) {
	t.Parallel()

	transport := &stubRoundTripper{
		responses: map[string]stubResponse{
			"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
			"/scripts/XML_daily.asp": {
				body: strings.ReplaceAll(
					dailyRatesXML, "<CharCode>USD</CharCode>", "<CharCode>XXX</CharCode>",
				),
			},
		},
	}

	conn, err := NewConnector(zap.NewNop(), map[string]any{
		"base_url":           "http://cbr.test",
		"http_round_tripper": transport,
	})
	if err != nil {
		t.Fatalf("NewConnector returned error: %v", err)
	}

	if _, err := conn.FetchTicker(context.Background(), "USD"); err == nil {
		t.Fatalf("FetchTicker expected to fail due to mismatch")
	}
}

func TestFetchTickerHistorical(t *testing.T) {
	t.Parallel()

	transport := &stubRoundTripper{
		responses: map[string]stubResponse{
			"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
			"/scripts/XML_dynamic.asp": {
				body: dynamicRatesXML,
				check: func(r *http.Request) error {
					if got := r.URL.Query().Get("VAL_NM_RQ"); got != "R01235" {
						return fmt.Errorf("unexpected valute id: %s", got) //nolint:err113
					}
					if got := r.URL.Query().Get("date_req1"); got != "05/01/2023" {
						return fmt.Errorf("unexpected date_req1: %s", got) //nolint:err113
					}
					if got := r.URL.Query().Get("date_req2"); got != "05/01/2023" {
						return fmt.Errorf("unexpected date_req2: %s", got) //nolint:err113
					}
					return nil
				},
			},
		},
	}

	conn, err := NewConnector(zap.NewNop(), map[string]any{
		"base_url":           "http://cbr.test",
		"http_round_tripper": transport,
	})
	if err != nil {
		t.Fatalf("NewConnector returned error: %v", err)
	}

	ticker, err := conn.FetchTicker(context.Background(), "USD@2023-01-05")
	if err != nil {
		t.Fatalf("FetchTicker returned error: %v", err)
	}

	if ticker.BidPrice != "70.10000000" || ticker.AskPrice != "70.10000000" {
		t.Fatalf("unexpected bid/ask: %s / %s", ticker.BidPrice, ticker.AskPrice)
	}
	if ticker.Symbol != "USD@2023-01-05" {
		t.Fatalf("unexpected symbol: %s", ticker.Symbol)
	}
}

func TestFetchTickerUnknownCurrency(t *testing.T) {
	t.Parallel()

	transport := &stubRoundTripper{
		responses: map[string]stubResponse{
			"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
			"/scripts/XML_daily.asp":   {body: dailyRatesXML},
		},
	}

	conn, err := NewConnector(zap.NewNop(), map[string]any{
		"base_url":           "http://cbr.test",
		"http_round_tripper": transport,
	})
	if err != nil {
		t.Fatalf("NewConnector returned error: %v", err)
	}

	_, err = conn.FetchTicker(context.Background(), "ZZZ")
	if err == nil {
		t.Fatalf("FetchTicker expected to fail for unknown currency")
	}
	if !errors.Is(err, merrors.ErrInvalidArg) {
		t.Fatalf("expected invalid argument error, got %v", err)
	}
}

func TestFetchTickerRespectsCustomPaths(t *testing.T) {
	t.Parallel()

	transport := &stubRoundTripper{
		responses: map[string]stubResponse{
			"/dir.xml":   {body: valuteDirectoryXML},
			"/rates.xml": {body: dailyRatesXML},
		},
	}

	conn, err := NewConnector(zap.NewNop(), map[string]any{
		"base_url":           "http://cbr.test",
		"directory_path":     "/dir.xml",
		"daily_path":         "/rates.xml",
		"http_round_tripper": transport,
	})
	if err != nil {
		t.Fatalf("NewConnector returned error: %v", err)
	}

	if _, err := conn.FetchTicker(context.Background(), "USD"); err != nil {
		t.Fatalf("FetchTicker returned error with custom paths: %v", err)
	}
}

const valuteDirectoryXML = `
<Valuta name="Foreign Currency Market">
  <Item ID="R01235">
    <ISO_Num_Code>840</ISO_Num_Code>
    <ISO_Char_Code>USD</ISO_Char_Code>
    <Nominal>1</Nominal>
    <Name>US Dollar</Name>
    <EngName>US Dollar</EngName>
  </Item>
</Valuta>`

const dailyRatesXML = `
<ValCurs Date="02.09.2024" name="Foreign Currency Market">
  <Valute ID="R01235">
    <NumCode>840</NumCode>
    <CharCode>USD</CharCode>
    <Nominal>1</Nominal>
    <Name>US Dollar</Name>
    <Value>95,1234</Value>
  </Valute>
</ValCurs>`

const dynamicRatesXML = `
<ValCurs ID="R01235" DateRange1="05/01/2023" DateRange2="05/01/2023" name="Foreign Currency Market Dynamic">
  <Record Date="05.01.2023" Id="R01235">
    <Nominal>1</Nominal>
    <Value>70,1</Value>
  </Record>
</ValCurs>`

type stubResponse struct {
	status int
	body   string
	check  func(*http.Request) error
}

type stubRoundTripper struct {
	responses map[string]stubResponse
}

func (s *stubRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	if s.responses == nil {
		return nil, fmt.Errorf("no responses configured") //nolint:err113
	}
	res, ok := s.responses[req.URL.Path]
	if !ok {
		return nil, fmt.Errorf("unexpected request path: %s", req.URL.Path) //nolint:err113
	}
	if res.check != nil {
		if err := res.check(req); err != nil {
			return nil, err
		}
	}

	status := res.status
	if status == 0 {
		status = http.StatusOK
	}

	return &http.Response{
		StatusCode: status,
		Body:       io.NopCloser(strings.NewReader(res.body)),
		Header:     http.Header{"Content-Type": []string{"text/xml"}},
		Request:    req,
	}, nil
}
