package handler

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

	"nex/backend/internal/domain"
	"nex/backend/tests/mocks"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	"gorm.io/gorm"

	appErrors "nex/backend/pkg/errors"
)

func init() {
	gin.SetMode(gin.TestMode)
}

func TestProviderHandler_CreateProvider_MissingFields(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	h := NewProviderHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{"id": "p1"})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/providers", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateProvider(c)
	assert.Equal(t, 400, w.Code)
}

func TestProviderHandler_ListProviders(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().List().Return([]domain.Provider{
		{ID: "p1", Name: "P1"},
		{ID: "p2", Name: "P2"},
	}, nil)
	h := NewProviderHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/providers", nil)

	h.ListProviders(c)
	assert.Equal(t, 200, w.Code)

	var result []domain.Provider
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.Len(t, result, 2)
}

func TestProviderHandler_GetProvider(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().Get(gomock.Eq("p1")).Return(&domain.Provider{ID: "p1", Name: "P1", APIKey: "sk-test"}, nil)
	h := NewProviderHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "p1"}}
	c.Request = httptest.NewRequest("GET", "/api/providers/p1", nil)

	h.GetProvider(c)
	assert.Equal(t, 200, w.Code)
}

func TestModelHandler_CreateModel_MissingFields(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{"id": "m1"})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/models", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateModel(c)
	assert.Equal(t, 400, w.Code)
}

func TestModelHandler_ListModels(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().List(gomock.Eq("")).Return([]domain.Model{
		{ID: "m1", ProviderID: "openai", ModelName: "gpt-4"},
		{ID: "m2", ProviderID: "anthropic", ModelName: "claude-3"},
	}, nil)
	h := NewModelHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/models", nil)

	h.ListModels(c)
	assert.Equal(t, 200, w.Code)

	var result []modelResponse
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	require.Len(t, result, 2)
	assert.Equal(t, "openai/gpt-4", result[0].UnifiedModelID)
	assert.Equal(t, "anthropic/claude-3", result[1].UnifiedModelID)
}

func TestModelHandler_GetModel_UnifiedID(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Get(gomock.Eq("m1")).Return(&domain.Model{ID: "m1", ProviderID: "openai", ModelName: "gpt-4"}, nil)
	h := NewModelHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "m1"}}
	c.Request = httptest.NewRequest("GET", "/api/models/m1", nil)

	h.GetModel(c)
	assert.Equal(t, 200, w.Code)

	var result modelResponse
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.Equal(t, "m1", result.ID)
	assert.Equal(t, "openai/gpt-4", result.UnifiedModelID)
}

func TestModelHandler_CreateModel_UnifiedID(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Create(gomock.Any()).DoAndReturn(func(model *domain.Model) error {
		model.ID = "mock-uuid-1234"
		return nil
	})
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{
		"provider_id": "openai",
		"model_name":  "gpt-4",
	})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/models", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateModel(c)
	assert.Equal(t, 201, w.Code)

	var result modelResponse
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.Equal(t, "mock-uuid-1234", result.ID)
	assert.Equal(t, "openai/gpt-4", result.UnifiedModelID)
}

func TestModelHandler_UpdateModel_UnifiedID(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Update(gomock.Eq("m1"), gomock.Any()).Return(nil)
	mockSvc.EXPECT().Get(gomock.Eq("m1")).Return(&domain.Model{ID: "m1", ProviderID: "openai", ModelName: "gpt-4-turbo"}, nil)
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]interface{}{"enabled": false})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "m1"}}
	c.Request = httptest.NewRequest("PUT", "/api/models/m1", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.UpdateModel(c)
	assert.Equal(t, 200, w.Code)

	var result modelResponse
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.Equal(t, "openai/gpt-4-turbo", result.UnifiedModelID)
}

func TestStatsHandler_GetStats(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockStatsService(ctrl)
	mockSvc.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]domain.UsageStats{
		{ProviderID: "p1", ModelName: "gpt-4", RequestCount: 10},
	}, nil)
	h := NewStatsHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/stats", nil)

	h.GetStats(c)
	assert.Equal(t, 200, w.Code)
}

func TestStatsHandler_GetStats_InvalidDate(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockStatsService(ctrl)
	h := NewStatsHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/stats?start_date=invalid", nil)

	h.GetStats(c)
	assert.Equal(t, 400, w.Code)
}

func TestStatsHandler_AggregateStats(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockStatsService(ctrl)
	mockSvc.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]domain.UsageStats{
		{ProviderID: "p1", RequestCount: 10},
	}, nil)
	mockSvc.EXPECT().Aggregate(gomock.Any(), gomock.Eq("provider")).Return([]map[string]interface{}{
		{"provider_id": "p1", "request_count": 10},
	})
	h := NewStatsHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/stats/aggregate?group_by=provider", nil)

	h.AggregateStats(c)
	assert.Equal(t, 200, w.Code)
}

func TestWriteError(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/", nil)

	writeError(c, appErrors.ErrModelNotFound)
	assert.Equal(t, 404, w.Code)
}

func TestFormatValidationErrors(t *testing.T) {
	errs := map[string]string{
		"model":    "模型名称不能为空",
		"messages": "消息列表不能为空",
	}
	result := formatMapErrors(errs)
	require.Contains(t, result, "请求验证失败")
	require.Contains(t, result, "model")
	require.Contains(t, result, "messages")
}

func formatMapErrors(errs map[string]string) string {
	parts := make([]string, 0, len(errs))
	for field, msg := range errs {
		parts = append(parts, fmt.Sprintf("%s: %s", field, msg))
	}
	return "请求验证失败: " + strings.Join(parts, "; ")
}

func TestProviderHandler_CreateProvider_DuplicatedKey(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().Create(gomock.Any()).Return(appErrors.ErrConflict)
	h := NewProviderHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{
		"id":       "p1",
		"name":     "Test",
		"api_key":  "sk-test",
		"base_url": "https://test.com",
	})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/providers", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateProvider(c)
	assert.Equal(t, 409, w.Code)
}

func TestModelHandler_CreateModel_ProviderNotFound(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Create(gomock.Any()).Return(appErrors.ErrProviderNotFound)
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{
		"provider_id": "nonexistent",
		"model_name":  "gpt-4",
	})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/models", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateModel(c)
	assert.Equal(t, 400, w.Code)
	assert.Contains(t, w.Body.String(), "供应商不存在")
}

func TestModelHandler_CreateModel_DuplicateModel(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Create(gomock.Any()).Return(appErrors.ErrDuplicateModel)
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{
		"provider_id": "openai",
		"model_name":  "gpt-4",
	})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/models", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateModel(c)
	assert.Equal(t, 409, w.Code)
	assert.Contains(t, w.Body.String(), "同一供应商下模型名称已存在")
}

func TestModelHandler_CreateModel_InternalError(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Create(gomock.Any()).Return(errors.New("database error"))
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{
		"provider_id": "openai",
		"model_name":  "gpt-4",
	})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/models", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateModel(c)
	assert.Equal(t, 500, w.Code)
}

func TestProviderHandler_UpdateProvider_NotFound(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().Update(gomock.Eq("p1"), gomock.Any()).Return(gorm.ErrRecordNotFound)
	h := NewProviderHandler(mockSvc)

	body, _ := json.Marshal(map[string]interface{}{"name": "Updated"})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "p1"}}
	c.Request = httptest.NewRequest("PUT", "/api/providers/p1", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.UpdateProvider(c)
	assert.Equal(t, 404, w.Code)
}

func TestProviderHandler_UpdateProvider_ImmutableField(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().Update(gomock.Eq("p1"), gomock.Any()).Return(appErrors.ErrImmutableField)
	h := NewProviderHandler(mockSvc)

	body, _ := json.Marshal(map[string]interface{}{"name": "Updated"})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "p1"}}
	c.Request = httptest.NewRequest("PUT", "/api/providers/p1", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.UpdateProvider(c)
	assert.Equal(t, 400, w.Code)
	assert.Contains(t, w.Body.String(), "供应商 ID 不允许修改")
}

func TestProviderHandler_UpdateProvider_InternalError(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().Update(gomock.Eq("p1"), gomock.Any()).Return(errors.New("database error"))
	h := NewProviderHandler(mockSvc)

	body, _ := json.Marshal(map[string]interface{}{"name": "Updated"})
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "p1"}}
	c.Request = httptest.NewRequest("PUT", "/api/providers/p1", bytes.NewReader(body))
	c.Request.Header.Set("Content-Type", "application/json")

	h.UpdateProvider(c)
	assert.Equal(t, 500, w.Code)
}

func TestModelHandler_CreateModel_InvalidJSON(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockModelService(ctrl)
	h := NewModelHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/models", bytes.NewReader([]byte("{invalid json")))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateModel(c)
	assert.Equal(t, 400, w.Code)
}

func TestProviderHandler_CreateProvider_InvalidJSON(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := mocks.NewMockProviderService(ctrl)
	h := NewProviderHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/api/providers", bytes.NewReader([]byte("{invalid json")))
	c.Request.Header.Set("Content-Type", "application/json")

	h.CreateProvider(c)
	assert.Equal(t, 400, w.Code)
}
