package gateway

import (
	"context"
	"sync"
	"testing"

	"github.com/tech/sendico/gateway/tgsettle/storage"
	storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
	envelope "github.com/tech/sendico/pkg/messaging/envelope"
	tnotifications "github.com/tech/sendico/pkg/messaging/notifications/telegram"
	mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

//
// FAKE STORES
//

type fakePaymentsStore struct {
	mu      sync.Mutex
	records map[string]*storagemodel.PaymentRecord
}

func (f *fakePaymentsStore) FindByIdempotencyKey(_ context.Context, key string) (*storagemodel.PaymentRecord, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.records == nil {
		return nil, nil
	}
	return f.records[key], nil
}

func (f *fakePaymentsStore) Upsert(_ context.Context, record *storagemodel.PaymentRecord) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.records == nil {
		f.records = map[string]*storagemodel.PaymentRecord{}
	}
	f.records[record.IdempotencyKey] = record
	return nil
}

type fakeTelegramStore struct {
	mu      sync.Mutex
	records map[string]*storagemodel.TelegramConfirmation
}

func (f *fakeTelegramStore) Upsert(_ context.Context, record *storagemodel.TelegramConfirmation) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.records == nil {
		f.records = map[string]*storagemodel.TelegramConfirmation{}
	}
	f.records[record.RequestID] = record
	return nil
}

type fakeRepo struct {
	payments *fakePaymentsStore
	tg       *fakeTelegramStore
}

func (f *fakeRepo) Payments() storage.PaymentsStore {
	return f.payments
}

func (f *fakeRepo) TelegramConfirmations() storage.TelegramConfirmationsStore {
	return f.tg
}

//
// FAKE BROKER (ОБЯЗАТЕЛЕН ДЛЯ СЕРВИСА)
//

type fakeBroker struct{}

func (f *fakeBroker) Publish(_ envelope.Envelope) error {
	return nil
}

func (f *fakeBroker) Subscribe(event model.NotificationEvent) (<-chan envelope.Envelope, error) {
	return nil, nil
}

func (f *fakeBroker) Unsubscribe(event model.NotificationEvent, subChan <-chan envelope.Envelope) error {
	return nil
}

//
// CAPTURE ONLY TELEGRAM REACTIONS
//

type captureProducer struct {
	mu        sync.Mutex
	reactions []envelope.Envelope
	sig       string
}

func (c *captureProducer) SendMessage(env envelope.Envelope) error {
	if env.GetSignature().ToString() != c.sig {
		return nil
	}
	c.mu.Lock()
	c.reactions = append(c.reactions, env)
	c.mu.Unlock()
	return nil
}

//
// TESTS
//

func newTestService(_ *testing.T) (*Service, *fakeRepo, *captureProducer) {
	logger := mloggerfactory.NewLogger(false)

	repo := &fakeRepo{
		payments: &fakePaymentsStore{},
		tg:       &fakeTelegramStore{},
	}

	sigEnv := tnotifications.TelegramReaction(string(mservice.PaymentGateway), &model.TelegramReactionRequest{
		RequestID: "x",
		ChatID:    "1",
		MessageID: "2",
		Emoji:     "ok",
	})

	prod := &captureProducer{
		sig: sigEnv.GetSignature().ToString(),
	}

	svc := NewService(logger, repo, prod, &fakeBroker{}, Config{
		Rail:            "card",
		SuccessReaction: "👍",
	})

	return svc, repo, prod
}

func TestConfirmed(t *testing.T) {
	svc, repo, prod := newTestService(t)

	_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
		IdempotencyKey:  "idem-1",
		PaymentIntentID: "pi-1",
		QuoteRef:        "quote-1",
		OutgoingLeg:     "card",
		RequestedMoney:  &paymenttypes.Money{Amount: "5", Currency: "EUR"},
		Status:          storagemodel.PaymentStatusWaiting,
	})

	result := &model.ConfirmationResult{
		RequestID: "idem-1",
		Money:     &paymenttypes.Money{Amount: "5", Currency: "EUR"},
		Status:    model.ConfirmationStatusConfirmed,
		RawReply:  &model.TelegramMessage{ChatID: "1", MessageID: "2"},
	}

	_ = svc.onConfirmationResult(context.Background(), result)

	rec := repo.payments.records["idem-1"]

	if rec.Status != storagemodel.PaymentStatusSuccess {
		t.Fatalf("expected success, got %s", rec.Status)
	}
	if rec.RequestedMoney == nil {
		t.Fatalf("requested money not set")
	}
	if rec.ExecutedAt.IsZero() {
		t.Fatalf("executedAt not set")
	}
	if repo.tg.records["idem-1"] == nil {
		t.Fatalf("telegram confirmation not stored")
	}
	if len(prod.reactions) != 1 {
		t.Fatalf("reaction must be published")
	}
}

func TestClarified(t *testing.T) {
	svc, repo, prod := newTestService(t)

	_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
		IdempotencyKey: "idem-2",
		Status:         storagemodel.PaymentStatusWaiting,
	})

	result := &model.ConfirmationResult{
		RequestID: "idem-2",
		Status:    model.ConfirmationStatusClarified,
		RawReply:  &model.TelegramMessage{ChatID: "1", MessageID: "2"},
	}

	_ = svc.onConfirmationResult(context.Background(), result)

	rec := repo.payments.records["idem-2"]

	if rec.Status != storagemodel.PaymentStatusWaiting {
		t.Fatalf("clarified must not change status")
	}
	if repo.tg.records["idem-2"] == nil {
		t.Fatalf("telegram confirmation must be stored")
	}
	if len(prod.reactions) != 0 {
		t.Fatalf("clarified must not publish reaction")
	}
}

func TestRejected(t *testing.T) {
	svc, repo, prod := newTestService(t)

	// ВАЖНО: чтобы текущий emitTransferStatusEvent не падал на nil,
	// даем минимально ожидаемые поля + non-nil ExecutedMoney.
	_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
		IdempotencyKey:  "idem-3",
		PaymentIntentID: "pi-3",
		QuoteRef:        "quote-3",
		OutgoingLeg:     "card",
		RequestedMoney:  &paymenttypes.Money{Amount: "5", Currency: "EUR"},
		ExecutedMoney:   &paymenttypes.Money{Amount: "0", Currency: "EUR"},
		Status:          storagemodel.PaymentStatusWaiting,
	})

	result := &model.ConfirmationResult{
		RequestID: "idem-3",
		Status:    model.ConfirmationStatusRejected,
		RawReply:  &model.TelegramMessage{ChatID: "1", MessageID: "2"},
	}

	_ = svc.onConfirmationResult(context.Background(), result)

	rec := repo.payments.records["idem-3"]

	if rec.Status != storagemodel.PaymentStatusFailed {
		t.Fatalf("expected failed")
	}
	if repo.tg.records["idem-3"] == nil {
		t.Fatalf("telegram confirmation must be stored")
	}
	if len(prod.reactions) != 0 {
		t.Fatalf("rejected must not publish reaction")
	}
}

func TestTimeout(t *testing.T) {
	svc, repo, prod := newTestService(t)

	// ВАЖНО: чтобы текущий emitTransferStatusEvent не падал на nil,
	// даем минимально ожидаемые поля + non-nil ExecutedMoney.
	_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
		IdempotencyKey:  "idem-4",
		PaymentIntentID: "pi-4",
		QuoteRef:        "quote-4",
		OutgoingLeg:     "card",
		RequestedMoney:  &paymenttypes.Money{Amount: "5", Currency: "EUR"},
		ExecutedMoney:   &paymenttypes.Money{Amount: "0", Currency: "EUR"},
		Status:          storagemodel.PaymentStatusWaiting,
	})

	result := &model.ConfirmationResult{
		RequestID: "idem-4",
		Status:    model.ConfirmationStatusTimeout,
		RawReply:  &model.TelegramMessage{ChatID: "1", MessageID: "2"},
	}

	_ = svc.onConfirmationResult(context.Background(), result)

	rec := repo.payments.records["idem-4"]

	if rec.Status != storagemodel.PaymentStatusFailed {
		t.Fatalf("timeout must be failed")
	}
	if repo.tg.records["idem-4"] == nil {
		t.Fatalf("telegram confirmation must be stored")
	}
	if len(prod.reactions) != 0 {
		t.Fatalf("timeout must not publish reaction")
	}
}
