package handler

import (
	"context"
	"database/sql"
	"errors"
	"net/http/httptest"
	"testing"

	"github.com/brianvoe/gofakeit/v6"
	"github.com/gofiber/fiber/v2"
	"github.com/gojuno/minimock/v3"
	"github.com/stretchr/testify/assert"

	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helper/test"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/model"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/handler/mocks"
)

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

	type req struct {
		method string
		route  string
	}

	var (
		tagID       = gofakeit.Uint64()
		tagURL      = gofakeit.Word()
		publishTime = gofakeit.Date()
		internalErr = errors.New(gofakeit.Phrase())

		tag = model.Tag{
			ID: tagID,
		}

		article = model.ArticlePreview{
			ID:          gofakeit.Uint64(),
			URL:         gofakeit.URL(),
			Title:       gofakeit.Phrase(),
			PublishTime: publishTime,
			PreviewText: sql.NullString{Valid: true, String: gofakeit.Phrase()},
			Image:       gofakeit.URL(),
		}
	)

	tests := []struct {
		name        string
		req         req
		res         int
		err         error
		cacheMock   func(mc *minimock.Controller) CacheService
		tagMock     func(mc *minimock.Controller) TagRepository
		articleMock func(mc *minimock.Controller) ArticleRepository
	}{
		{
			name: "positive case",
			req: req{
				method: fiber.MethodGet,
				route:  "/tag/" + tagURL,
			},
			res: fiber.StatusOK,
			err: nil,
			cacheMock: func(mc *minimock.Controller) CacheService {
				mock := mocks.NewCacheServiceMock(mc)
				mock.GetMock.Return(nil, false)
				mock.SetMock.Return()
				return mock
			},
			tagMock: func(mc *minimock.Controller) TagRepository {
				mock := mocks.NewTagRepositoryMock(mc)

				mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
					assert.Equal(mc, tagURL, url)
				}).Return(&tag, nil)

				return mock

			},
			articleMock: func(mc *minimock.Controller) ArticleRepository {
				mock := mocks.NewArticleRepositoryMock(mc)

				mock.GetPreviewByTagIDMock.Inspect(func(ctx context.Context, id uint64, lang model.LanguageID) {
					assert.Equal(mc, tagID, id)
				}).Return([]model.ArticlePreview{article}, nil)

				return mock
			},
		},
		{
			name: "negative case - tag not found",
			req: req{
				method: fiber.MethodGet,
				route:  "/tag/" + tagURL,
			},
			res: fiber.StatusNotFound,
			err: nil,
			cacheMock: func(mc *minimock.Controller) CacheService {
				mock := mocks.NewCacheServiceMock(mc)
				mock.GetMock.Return(nil, false)
				return mock
			},
			tagMock: func(mc *minimock.Controller) TagRepository {
				mock := mocks.NewTagRepositoryMock(mc)

				mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
					assert.Equal(mc, tagURL, url)
				}).Return(nil, sql.ErrNoRows)

				return mock

			},
			articleMock: func(mc *minimock.Controller) ArticleRepository {
				return mocks.NewArticleRepositoryMock(mc)
			},
		},
		{
			name: "negative case - tag repository error",
			req: req{
				method: fiber.MethodGet,
				route:  "/tag/" + tagURL,
			},
			res: fiber.StatusInternalServerError,
			err: nil,
			cacheMock: func(mc *minimock.Controller) CacheService {
				mock := mocks.NewCacheServiceMock(mc)
				mock.GetMock.Return(nil, false)
				return mock
			},
			tagMock: func(mc *minimock.Controller) TagRepository {
				mock := mocks.NewTagRepositoryMock(mc)

				mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
					assert.Equal(mc, tagURL, url)
				}).Return(nil, internalErr)

				return mock
			},
			articleMock: func(mc *minimock.Controller) ArticleRepository {
				return mocks.NewArticleRepositoryMock(mc)
			},
		},
		{
			name: "negative case - article repository error",
			req: req{
				method: fiber.MethodGet,
				route:  "/tag/" + tagURL,
			},
			res: fiber.StatusInternalServerError,
			err: nil,
			cacheMock: func(mc *minimock.Controller) CacheService {
				mock := mocks.NewCacheServiceMock(mc)
				mock.GetMock.Return(nil, false)
				return mock
			},
			tagMock: func(mc *minimock.Controller) TagRepository {
				mock := mocks.NewTagRepositoryMock(mc)

				mock.GetByURLMock.Inspect(func(ctx context.Context, url string) {
					assert.Equal(mc, tagURL, url)
				}).Return(&tag, nil)

				return mock

			},
			articleMock: func(mc *minimock.Controller) ArticleRepository {
				mock := mocks.NewArticleRepositoryMock(mc)

				mock.GetPreviewByTagIDMock.Inspect(func(ctx context.Context, id uint64, lang model.LanguageID) {
					assert.Equal(mc, tagID, id)
				}).Return(nil, internalErr)

				return mock
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			mc := minimock.NewController(t)
			fiberApp := fiber.New(test.GetFiberTestConfig())
			fiberReq := httptest.NewRequest(tt.req.method, tt.req.route, nil)

			fiberApp.Get("/tag/:tag", NewTagPageHandler(
				tt.cacheMock(mc),
				tt.articleMock(mc),
				tt.tagMock(mc),
			))

			fiberRes, fiberErr := fiberApp.Test(fiberReq)
			assert.Equal(t, tt.res, fiberRes.StatusCode)
			assert.Equal(t, tt.err, fiberErr)
		})
	}
}
