package service_test

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/charmingruby/homunc/apps/fraud/internal/fraud/delivery/event/publisher"
	"github.com/charmingruby/homunc/apps/fraud/internal/fraud/mocks"
	"github.com/charmingruby/homunc/apps/fraud/internal/fraud/model"
	"github.com/charmingruby/homunc/apps/fraud/internal/fraud/service"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func Test_Service_CheckTransferAvailability_Secured(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	in := service.CheckTransferAvailabilityInput{
		UserID:      "user-1",
		SessionID:   "session-1",
		Country:     "BR",
		Amount:      4000,
		TriggeredAt: time.Date(2025, 1, 1, 15, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(model.BlockedSource{}, nil)
	pub.On("DispatchCheckoutAvailabilityStatus", mock.Anything, publisher.SecuredStatus, mock.Anything).Return(nil)

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.NoError(t, err)
}

func Test_Service_CheckTransferAvailability_FirstViolation(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	in := service.CheckTransferAvailabilityInput{
		UserID:      "user-2",
		SessionID:   "session-2",
		Country:     "BR",
		Amount:      6000,
		TriggeredAt: time.Date(2025, 1, 1, 23, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(model.BlockedSource{}, nil)
	repo.On("Create", mock.MatchedBy(func(b model.BlockedSource) bool {
		return b.Attempts == 1 && b.UserID == in.UserID
	})).Return(nil)

	pub.On("DispatchCheckoutAvailabilityStatus", mock.Anything, publisher.WarnedStatus, mock.Anything).Return(nil)

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.NoError(t, err)
}

func Test_Service_CheckTransferAvailability_SecondViolation(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	existing := model.BlockedSource{
		ID:        "id-2",
		UserID:    "user-2",
		SessionID: "session-2",
		Country:   "BR",
		Attempts:  1,
	}

	in := service.CheckTransferAvailabilityInput{
		UserID:      existing.UserID,
		SessionID:   existing.SessionID,
		Country:     existing.Country,
		Amount:      6000,
		TriggeredAt: time.Date(2025, 1, 1, 23, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(existing, nil)
	repo.On("Update", mock.MatchedBy(func(b model.BlockedSource) bool {
		return b.Attempts == 2 && b.WarnedAt != nil
	})).Return(nil)

	pub.On("DispatchCheckoutAvailabilityStatus", mock.Anything, publisher.WarnedStatus, mock.Anything).Return(nil)

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.NoError(t, err)
}

func Test_Service_CheckTransferAvailability_ThirdViolation_Block(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	existing := model.BlockedSource{
		ID:        "id-3",
		UserID:    "user-3",
		SessionID: "session-3",
		Country:   "BR",
		Attempts:  2,
	}

	in := service.CheckTransferAvailabilityInput{
		UserID:      existing.UserID,
		SessionID:   existing.SessionID,
		Country:     existing.Country,
		Amount:      6000,
		TriggeredAt: time.Date(2025, 1, 1, 23, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(existing, nil)
	repo.On("Update", mock.MatchedBy(func(b model.BlockedSource) bool {
		return b.Attempts == 3 && b.BlockedUntil != nil
	})).Return(nil)

	pub.On("DispatchCheckoutAvailabilityStatus", mock.Anything, publisher.BlockedStatus, mock.Anything).Return(nil)

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.NoError(t, err)
}

func Test_Service_CheckTransferAvailability_ExpiredBlock(t *testing.T) {
	now := time.Now()
	expired := now.Add(-time.Hour)

	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	existing := model.BlockedSource{
		ID:           "id-4",
		UserID:       "user-4",
		SessionID:    "session-4",
		Country:      "BR",
		Attempts:     3,
		BlockedUntil: &expired,
	}

	in := service.CheckTransferAvailabilityInput{
		UserID:      existing.UserID,
		SessionID:   existing.SessionID,
		Country:     existing.Country,
		Amount:      4000,
		TriggeredAt: now,
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(existing, nil)
	repo.On("Update", mock.Anything).Return(nil)
	pub.On("DispatchCheckoutAvailabilityStatus", mock.Anything, publisher.SecuredStatus, mock.Anything).Return(nil)

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.NoError(t, err)
}

func Test_Service_CheckTransferAvailability_ErroNoRepo(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	in := service.CheckTransferAvailabilityInput{
		UserID:      "user-err",
		SessionID:   "session-err",
		Country:     "BR",
		Amount:      4000,
		TriggeredAt: time.Now(),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(model.BlockedSource{}, errors.New("db error"))

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "db error")
}

func Test_Service_CheckTransferAvailability_ErroCreate(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	in := service.CheckTransferAvailabilityInput{
		UserID:      "user-x",
		SessionID:   "session-x",
		Country:     "BR",
		Amount:      6000,
		TriggeredAt: time.Date(2025, 1, 1, 23, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(model.BlockedSource{}, nil)
	repo.On("Create", mock.Anything).Return(errors.New("create error"))

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "create error")
}

func Test_Service_CheckTransferAvailability_ErroUpdate(t *testing.T) {
	existing := model.BlockedSource{
		ID:        "id-5",
		UserID:    "user-5",
		SessionID: "session-5",
		Country:   "BR",
		Attempts:  1,
	}

	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	in := service.CheckTransferAvailabilityInput{
		UserID:      existing.UserID,
		SessionID:   existing.SessionID,
		Country:     existing.Country,
		Amount:      6000,
		TriggeredAt: time.Date(2025, 1, 1, 23, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(existing, nil)
	repo.On("Update", mock.Anything).Return(errors.New("update error"))

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "update error")
}

func Test_Service_CheckTransferAvailability_ErroDispatch(t *testing.T) {
	repo := mocks.NewBlockedSourceRepo(t)
	pub := mocks.NewPublisher(t)
	svc := service.New(repo, pub)

	in := service.CheckTransferAvailabilityInput{
		UserID:      "user-6",
		SessionID:   "session-6",
		Country:     "BR",
		Amount:      4000,
		TriggeredAt: time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC),
	}

	repo.On("FindByCheckoutDetails", mock.Anything).Return(model.BlockedSource{}, nil)
	pub.On("DispatchCheckoutAvailabilityStatus", mock.Anything, publisher.SecuredStatus, mock.Anything).Return(errors.New("dispatch error"))

	err := svc.CheckTransferAvailability(context.Background(), in)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "dispatch error")
}
