package graphql_test

import (
	"context"
	"fmt"
	"strconv"
	"testing"

	"tercul/internal/adapters/graphql"
	"tercul/internal/adapters/graphql/model"
	"tercul/internal/app"
	"tercul/internal/app/analytics"
	"tercul/internal/app/like"
	"tercul/internal/domain"
	platform_auth "tercul/internal/platform/auth"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
)

// LikeResolversUnitSuite is a unit test suite for the like resolvers.
type LikeResolversUnitSuite struct {
	suite.Suite
	resolver         *graphql.Resolver
	mockLikeRepo     *mockLikeRepository
	mockWorkRepo     *mockWorkRepository
	mockAnalyticsSvc *mockAnalyticsService
}

func (s *LikeResolversUnitSuite) SetupTest() {
	// 1. Create mock repositories
	s.mockLikeRepo = new(mockLikeRepository)
	s.mockWorkRepo = new(mockWorkRepository)
	s.mockAnalyticsSvc = new(mockAnalyticsService)

	// 2. Create real services with mock repositories
	analyticsService := analytics.NewService(s.mockAnalyticsSvc, nil, nil, s.mockWorkRepo, nil)
	likeService := like.NewService(s.mockLikeRepo, analyticsService)

	// 3. Create the resolver with the services
	s.resolver = &graphql.Resolver{
		App: &app.Application{
			Like:      likeService,
			Analytics: analyticsService,
		},
	}
}

func TestLikeResolversUnitSuite(t *testing.T) {
	suite.Run(t, new(LikeResolversUnitSuite))
}

func (s *LikeResolversUnitSuite) TestCreateLike() {
	// 1. Setup
	workIDStr := "1"
	workIDUint64, _ := strconv.ParseUint(workIDStr, 10, 32)
	workIDUint := uint(workIDUint64)
	userID := uint(123)

	// Mock repository responses
	s.mockWorkRepo.On("Exists", mock.Anything, workIDUint).Return(true, nil)
	s.mockLikeRepo.On("Create", mock.Anything, mock.AnythingOfType("*domain.Like")).Run(func(args mock.Arguments) {
		arg := args.Get(1).(*domain.Like)
		arg.ID = 1 // Simulate database assigning an ID
	}).Return(nil)
	s.mockAnalyticsSvc.On("IncrementWorkCounter", mock.Anything, workIDUint, "likes", 1).Return(nil)

	// Create a context with an authenticated user
	ctx := platform_auth.ContextWithUserID(context.Background(), userID)

	// 2. Execution
	likeInput := model.LikeInput{
		WorkID: &workIDStr,
	}
	createdLike, err := s.resolver.Mutation().CreateLike(ctx, likeInput)

	// 3. Assertions
	s.Require().NoError(err)
	s.Require().NotNil(createdLike)

	s.Equal("1", createdLike.ID)
	s.Equal(fmt.Sprintf("%d", userID), createdLike.User.ID)

	// Verify that the repository's Create method was called
	s.mockLikeRepo.AssertCalled(s.T(), "Create", mock.Anything, mock.MatchedBy(func(l *domain.Like) bool {
		return *l.WorkID == workIDUint && l.UserID == userID
	}))
	// Verify that analytics was called
	s.mockAnalyticsSvc.AssertCalled(s.T(), "IncrementWorkCounter", mock.Anything, workIDUint, "likes", 1)
}

func (s *LikeResolversUnitSuite) TestDeleteLike() {
	// 1. Setup
	likeIDStr := "1"
	likeIDUint, _ := strconv.ParseUint(likeIDStr, 10, 32)
	userID := uint(123)

	// Mock the repository response for the initial 'find'
	s.mockLikeRepo.On("GetByID", mock.Anything, uint(likeIDUint)).Return(&domain.Like{
		BaseModel: domain.BaseModel{ID: uint(likeIDUint)},
		UserID:    userID,
	}, nil)

	// Mock the repository response for the 'delete'
	s.mockLikeRepo.On("Delete", mock.Anything, uint(likeIDUint)).Return(nil)

	// Create a context with an authenticated user
	ctx := platform_auth.ContextWithUserID(context.Background(), userID)

	// 2. Execution
	deleted, err := s.resolver.Mutation().DeleteLike(ctx, likeIDStr)

	// 3. Assertions
	s.Require().NoError(err)
	s.True(deleted)

	// Verify that the repository's Delete method was called
	s.mockLikeRepo.AssertCalled(s.T(), "Delete", mock.Anything, uint(likeIDUint))
}