package handlers

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"goyco/internal/database"
	"goyco/internal/middleware"
	"goyco/internal/repositories"
	"goyco/internal/services"
	"goyco/internal/testutils"

	"github.com/go-chi/chi/v5"
)

func TestAPIHandlerGetAPIInfo(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockUserRepo := testutils.NewUserRepositoryStub()
	handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/api", nil)

	handler.GetAPIInfo(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	var resp APIInfo
	if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if !resp.Success || resp.Message == "" {
		t.Fatalf("expected success response, got %+v", resp)
	}

	data, ok := resp.Data.(map[string]any)
	if !ok || data["name"] != fmt.Sprintf("%s API", testutils.AppTestConfig.App.Title) {
		t.Fatalf("unexpected data payload: %#v", resp.Data)
	}

	endpoints, ok := data["endpoints"].(map[string]any)
	if !ok {
		t.Fatalf("expected endpoints map, got %#v", data["endpoints"])
	}

	authEndpoints := endpoints["authentication"].(map[string]any)
	for _, route := range []string{
		"POST /api/auth/resend-verification",
		"POST /api/auth/account/confirm",
	} {
		if _, found := authEndpoints[route]; !found {
			t.Fatalf("expected authentication catalogue to include %s", route)
		}
	}

	systemEndpoints := endpoints["system"].(map[string]any)
	if _, found := systemEndpoints["GET /metrics"]; !found {
		t.Fatalf("expected system catalogue to include GET /metrics")
	}
}

func TestAPIHandlerGetHealth(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockUserRepo := testutils.NewUserRepositoryStub()
	handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/health", nil)

	handler.GetHealth(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	var resp APIInfo
	if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
		t.Fatalf("decode error: %v", err)
	}

	if !resp.Success || resp.Message == "" {
		t.Fatalf("expected success message, got %+v", resp)
	}

	data := resp.Data.(map[string]any)
	if data["status"] != "healthy" {
		t.Fatalf("expected health status, got %+v", data)
	}
}

func TestAPIHandlerGetMetrics(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockPostRepo.CountFn = func() (int64, error) { return 10, nil }
	mockPostRepo.GetTopPostsFn = func(limit int) ([]database.Post, error) {
		return []database.Post{
			{ID: 1, Score: 100},
			{ID: 2, Score: 50},
			{ID: 3, Score: 25},
		}, nil
	}

	mockUserRepo := testutils.NewUserRepositoryStub()
	mockUserRepo.CountFn = func() (int64, error) { return 5, nil }

	handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)
	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/metrics", nil)

	handler.GetMetrics(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	var resp APIInfo
	if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
		t.Fatalf("decode error: %v", err)
	}

	if !resp.Success || resp.Message == "" {
		t.Fatalf("expected success response, got %+v", resp)
	}

	data, ok := resp.Data.(map[string]any)
	if !ok {
		t.Fatalf("expected metrics data map, got %T", resp.Data)
	}

	if data["posts"] == nil {
		t.Fatalf("expected metrics payload to include posts")
	}
	if data["users"] == nil {
		t.Fatalf("expected metrics payload to include users")
	}
	if data["votes"] == nil {
		t.Fatalf("expected metrics payload to include votes")
	}
	if data["system"] == nil {
		t.Fatalf("expected metrics payload to include system")
	}

	posts, ok := data["posts"].(map[string]any)
	if !ok {
		t.Fatalf("expected posts to be a map, got %T", data["posts"])
	}
	if posts["total_count"] != float64(10) {
		t.Fatalf("expected posts total_count to be 10, got %v", posts["total_count"])
	}
}

func newAPIHandlerForTest(postRepo repositories.PostRepository, userRepo repositories.UserRepository) *APIHandler {
	voteRepo := testutils.NewMockVoteRepository()
	voteService := services.NewVoteService(voteRepo, postRepo, nil)
	return NewAPIHandler(testutils.AppTestConfig, postRepo, userRepo, voteService)
}

func TestAPIHandlerGetMetricsErrorHandling(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockPostRepo.CountFn = func() (int64, error) { return 0, errors.New("database error") }

	mockUserRepo := testutils.NewUserRepositoryStub()
	handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/metrics", nil)

	handler.GetMetrics(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusInternalServerError)

	var resp APIInfo
	if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
		t.Fatalf("decode error: %v", err)
	}

	if resp.Success {
		t.Fatalf("expected error response, got %+v", resp)
	}
}

func TestAPIHandlerGetMetricsWithDatabaseMonitoring(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockPostRepo.CountFn = func() (int64, error) { return 10, nil }
	mockPostRepo.GetTopPostsFn = func(limit int) ([]database.Post, error) {
		return []database.Post{
			{ID: 1, Score: 100},
			{ID: 2, Score: 50},
		}, nil
	}

	mockUserRepo := testutils.NewUserRepositoryStub()
	mockUserRepo.CountFn = func() (int64, error) { return 5, nil }

	voteRepo := testutils.NewMockVoteRepository()
	voteService := services.NewVoteService(voteRepo, mockPostRepo, nil)

	handler := NewAPIHandler(testutils.AppTestConfig, mockPostRepo, mockUserRepo, voteService)

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/metrics", nil)

	handler.GetMetrics(recorder, request)

	testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

	var resp APIInfo
	if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
		t.Fatalf("decode error: %v", err)
	}

	if !resp.Success {
		t.Fatalf("expected success response, got %+v", resp)
	}

	data, ok := resp.Data.(map[string]any)
	if !ok {
		t.Fatalf("expected metrics data map, got %T", resp.Data)
	}

	expectedSections := []string{"posts", "users", "votes", "system"}
	for _, section := range expectedSections {
		if data[section] == nil {
			t.Fatalf("expected metrics payload to include %s", section)
		}
	}
}

func TestNewAPIHandlerWithMonitoring(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockUserRepo := testutils.NewUserRepositoryStub()
	voteRepo := testutils.NewMockVoteRepository()
	voteService := services.NewVoteService(voteRepo, mockPostRepo, nil)
	monitor := middleware.NewInMemoryDBMonitor()

	db := testutils.NewTestDB(t)
	defer func() {
		sqlDB, _ := db.DB()
		sqlDB.Close()
	}()

	handler := NewAPIHandlerWithMonitoring(testutils.AppTestConfig, mockPostRepo, mockUserRepo, voteService, db, monitor)

	if handler == nil {
		t.Fatal("Expected handler to be created")
	}

	if handler.dbMonitor == nil {
		t.Error("Expected dbMonitor to be set")
	}

	if handler.healthChecker == nil {
		t.Error("Expected healthChecker to be set")
	}

	if handler.metricsCollector == nil {
		t.Error("Expected metricsCollector to be set")
	}
}

func TestNewAPIHandlerWithMonitoring_NilDB(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockUserRepo := testutils.NewUserRepositoryStub()
	voteRepo := testutils.NewMockVoteRepository()
	voteService := services.NewVoteService(voteRepo, mockPostRepo, nil)

	handler := NewAPIHandlerWithMonitoring(testutils.AppTestConfig, mockPostRepo, mockUserRepo, voteService, nil, nil)

	if handler == nil {
		t.Fatal("Expected handler to be created")
	}

	if handler.dbMonitor != nil {
		t.Error("Expected dbMonitor to be nil when db is nil")
	}

	if handler.healthChecker != nil {
		t.Error("Expected healthChecker to be nil when db is nil")
	}

	if handler.metricsCollector != nil {
		t.Error("Expected metricsCollector to be nil when db is nil")
	}
}

func TestAPIHandlerMountRoutes(t *testing.T) {
	mockPostRepo := testutils.NewPostRepositoryStub()
	mockUserRepo := testutils.NewUserRepositoryStub()
	handler := newAPIHandlerForTest(mockPostRepo, mockUserRepo)

	router := chi.NewRouter()
	config := RouteModuleConfig{}

	router.Route("/api", func(r chi.Router) {
		handler.MountRoutes(r, config)
	})

	t.Run("GET route works", func(t *testing.T) {
		request := httptest.NewRequest(http.MethodGet, "/api", nil)
		recorder := httptest.NewRecorder()

		router.ServeHTTP(recorder, request)

		testutils.AssertHTTPStatus(t, recorder, http.StatusOK)

		var resp APIInfo
		if err := json.NewDecoder(recorder.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}

		if !resp.Success {
			t.Fatalf("expected success response, got %+v", resp)
		}
	})

	t.Run("only GET is mounted", func(t *testing.T) {
		methods := []string{http.MethodPost, http.MethodPut, http.MethodDelete}

		for _, method := range methods {
			request := httptest.NewRequest(method, "/api", nil)
			recorder := httptest.NewRecorder()

			router.ServeHTTP(recorder, request)

			if recorder.Code != http.StatusMethodNotAllowed && recorder.Code != http.StatusNotFound {
				t.Errorf("expected method not allowed or not found for %s, got %d", method, recorder.Code)
			}
		}
	})
}
