package gateway

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

	"github.com/tech/sendico/gateway/mntx/internal/service/monetix"
	"github.com/tech/sendico/gateway/mntx/storage/model"
	clockpkg "github.com/tech/sendico/pkg/clock"
	"github.com/tech/sendico/pkg/merrors"
	mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
	"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)
}

type staticClock struct {
	now time.Time
}

func (s staticClock) Now() time.Time {
	return s.now
}

func TestCardPayoutProcessor_Submit_Success(t *testing.T) {
	cfg := monetix.Config{
		BaseURL:           "https://monetix.test",
		SecretKey:         "secret",
		ProjectID:         99,
		AllowedCurrencies: []string{"RUB"},
	}

	existingCreated := time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC)

	repo := newMockRepository()
	repo.payouts.Save(&model.CardPayout{
		PayoutID:  "payout-1",
		CreatedAt: existingCreated,
	})

	httpClient := &http.Client{
		Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
			resp := monetix.APIResponse{}
			resp.Operation.RequestID = "req-123"
			body, _ := json.Marshal(resp)
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       io.NopCloser(bytes.NewReader(body)),
				Header:     http.Header{"Content-Type": []string{"application/json"}},
			}, nil
		}),
	}

	now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
	processor := newCardPayoutProcessor(zap.NewNop(), cfg, staticClock{now: now}, repo, httpClient, nil)

	req := validCardPayoutRequest()
	req.ProjectId = 0

	resp, err := processor.Submit(context.Background(), req)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if !resp.GetAccepted() {
		t.Fatalf("expected accepted payout response")
	}
	if resp.GetPayout().GetProjectId() != cfg.ProjectID {
		t.Fatalf("expected project id %d, got %d", cfg.ProjectID, resp.GetPayout().GetProjectId())
	}
	if resp.GetPayout().GetStatus() != mntxv1.PayoutStatus_PAYOUT_STATUS_WAITING {
		t.Fatalf("expected waiting status, got %v", resp.GetPayout().GetStatus())
	}
	if !resp.GetPayout().GetCreatedAt().AsTime().Equal(existingCreated) {
		t.Fatalf("expected created_at preserved, got %v", resp.GetPayout().GetCreatedAt().AsTime())
	}

	stored, ok := repo.payouts.Get(req.GetPayoutId())
	if !ok || stored == nil {
		t.Fatalf("expected payout state stored")
	}
	if stored.ProviderPaymentID == "" {
		t.Fatalf("expected provider payment id")
	}
	if !stored.CreatedAt.Equal(existingCreated) {
		t.Fatalf("expected created_at preserved in model, got %v", stored.CreatedAt)
	}
}

func TestCardPayoutProcessor_Submit_MissingConfig(t *testing.T) {
	cfg := monetix.Config{
		AllowedCurrencies: []string{"RUB"},
	}

	processor := newCardPayoutProcessor(
		zap.NewNop(),
		cfg,
		clockpkg.NewSystem(),
		newMockRepository(),
		&http.Client{},
		nil,
	)

	_, err := processor.Submit(context.Background(), validCardPayoutRequest())
	if err == nil {
		t.Fatalf("expected error")
	}
	if !errors.Is(err, merrors.ErrInternal) {
		t.Fatalf("expected internal error, got %v", err)
	}
}

func TestCardPayoutProcessor_ProcessCallback(t *testing.T) {
	cfg := monetix.Config{
		SecretKey:         "secret",
		StatusSuccess:     "success",
		StatusProcessing:  "processing",
		AllowedCurrencies: []string{"RUB"},
	}

	repo := newMockRepository()

	processor := newCardPayoutProcessor(
		zap.NewNop(),
		cfg,
		staticClock{now: time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)},
		repo,
		&http.Client{},
		nil,
	)

	cb := baseCallback()
	cb.Payment.Sum.Currency = "RUB"

	sig, err := monetix.SignPayload(cb, cfg.SecretKey)
	if err != nil {
		t.Fatalf("failed to sign callback: %v", err)
	}
	cb.Signature = sig

	payload, err := json.Marshal(cb)
	if err != nil {
		t.Fatalf("failed to marshal callback: %v", err)
	}

	status, err := processor.ProcessCallback(context.Background(), payload)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if status != http.StatusOK {
		t.Fatalf("expected status ok, got %d", status)
	}

	state, ok := repo.payouts.Get(cb.Payment.ID)
	if !ok || state == nil {
		t.Fatalf("expected payout state stored")
	}

	if state.Status != model.PayoutStatusSuccess {
		t.Fatalf("expected success status in model, got %v", state.Status)
	}
}
