package handler

import (
	"bytes"
	"encoding/json"
	"net/http/httptest"
	"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"
)

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

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

	body, _ := json.Marshal(map[string]string{
		"id":       "p1",
		"name":     "Test",
		"api_key":  "sk-test",
		"base_url": "https://api.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, 201, w.Code)

	var result domain.Provider
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.Equal(t, "p1", result.ID)
	assert.Equal(t, "sk-test", result.APIKey)
}

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

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

	body, _ := json.Marshal(map[string]string{
		"id":       "p1",
		"name":     "Test",
		"api_key":  "sk-test",
		"base_url": "https://api.test.com",
		"protocol": "anthropic",
	})
	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, 201, w.Code)
}

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

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

	body, _ := json.Marshal(map[string]string{"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, 200, w.Code)
}

func TestProviderHandler_UpdateProvider_InvalidBody(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.Params = gin.Params{{Key: "id", Value: "p1"}}
	c.Request = httptest.NewRequest("PUT", "/api/providers/p1", nil)

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

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

	mockSvc := mocks.NewMockProviderService(ctrl)
	mockSvc.EXPECT().Delete(gomock.Eq("p1")).Return(nil)
	h := NewProviderHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "p1"}}
	c.Request = httptest.NewRequest("DELETE", "/api/providers/p1", bytes.NewReader([]byte{}))
	c.Request.Header.Set("Content-Type", "application/json")

	h.DeleteProvider(c)
	assert.True(t, w.Code == 204 || w.Code == 200)
}

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

	mockSvc := mocks.NewMockModelService(ctrl)
	mockSvc.EXPECT().Delete(gomock.Eq("m1")).Return(nil)
	h := NewModelHandler(mockSvc)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Params = gin.Params{{Key: "id", Value: "m1"}}
	c.Request = httptest.NewRequest("DELETE", "/api/models/m1", bytes.NewReader([]byte{}))
	c.Request.Header.Set("Content-Type", "application/json")

	h.DeleteModel(c)
	assert.True(t, w.Code == 204 || w.Code == 200)
}

func TestModelHandler_CreateModel_Success(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": "p1",
		"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 domain.Model
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.NotEmpty(t, result.ID)
}

func TestModelHandler_GetModel(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", 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 domain.Model
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result))
	assert.Equal(t, "gpt-4", result.ModelName)
}

func TestModelHandler_UpdateModel(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", ModelName: "gpt-4o"}, nil)
	h := NewModelHandler(mockSvc)

	body, _ := json.Marshal(map[string]string{"model_name": "gpt-4o"})
	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)
}
