package dashboard

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/dbaas"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

func TestDBaaSRequestToService(t *testing.T) {
	t.Run("basic conversion", func(t *testing.T) {
		req := dto.DBaaSRequest{
			ID:       "dbs-123",
			Name:     "My Service",
			Provider: "tidb_cloud",
			APIKey:   "sk-test-key",
		}
		s := dbaasRequestToService(req)

		if s.ID != "dbs-123" {
			t.Errorf("ID = %q, want %q", s.ID, "dbs-123")
		}
		if s.Name != "My Service" {
			t.Errorf("Name = %q, want %q", s.Name, "My Service")
		}
		if s.Provider != "tidb_cloud" {
			t.Errorf("Provider = %q, want %q", s.Provider, "tidb_cloud")
		}
		if s.APIKey != "sk-test-key" {
			t.Errorf("APIKey = %q, want %q", s.APIKey, "sk-test-key")
		}
	})

	t.Run("empty optional fields", func(t *testing.T) {
		req := dto.DBaaSRequest{
			ID:       "dbs-456",
			Provider: "pingcap_cloud",
		}
		s := dbaasRequestToService(req)

		if s.Name != "" {
			t.Errorf("Name = %q, want empty", s.Name)
		}
		if s.APIKey != "" {
			t.Errorf("APIKey = %q, want empty", s.APIKey)
		}
	})
}

func TestToDBaaSResponse(t *testing.T) {
	t.Run("basic conversion", func(t *testing.T) {
		s := dbaas.Service{
			ID:       "dbs-789",
			Name:     "Test Service",
			Provider: "tem",
			APIKey:   "key-abc",
		}
		resp := toDBaaSResponse(s)

		if resp.ID != "dbs-789" {
			t.Errorf("ID = %q, want %q", resp.ID, "dbs-789")
		}
		if resp.Name != "Test Service" {
			t.Errorf("Name = %q, want %q", resp.Name, "Test Service")
		}
		if resp.Provider != "tem" {
			t.Errorf("Provider = %q, want %q", resp.Provider, "tem")
		}
		if resp.APIKey != "key-abc" {
			t.Errorf("APIKey = %q, want %q", resp.APIKey, "key-abc")
		}
	})

	t.Run("empty optional fields", func(t *testing.T) {
		s := dbaas.Service{
			ID:       "dbs-empty",
			Provider: "tidb_cloud",
		}
		resp := toDBaaSResponse(s)

		if resp.Name != "" {
			t.Errorf("Name = %q, want empty", resp.Name)
		}
		if resp.APIKey != "" {
			t.Errorf("APIKey = %q, want empty", resp.APIKey)
		}
	})

	t.Run("round-trip preserves all fields", func(t *testing.T) {
		req := dto.DBaaSRequest{
			ID:       "dbs-rt",
			Name:     "Round Trip",
			Provider: "pingcap_cloud",
			APIKey:   "rt-key",
		}
		s := dbaasRequestToService(req)
		resp := toDBaaSResponse(s)

		if resp.ID != req.ID {
			t.Errorf("ID = %q, want %q", resp.ID, req.ID)
		}
		if resp.Name != req.Name {
			t.Errorf("Name = %q, want %q", resp.Name, req.Name)
		}
		if resp.Provider != req.Provider {
			t.Errorf("Provider = %q, want %q", resp.Provider, req.Provider)
		}
		if resp.APIKey != req.APIKey {
			t.Errorf("APIKey = %q, want %q", resp.APIKey, req.APIKey)
		}
	})
}

// setupDBaaSRouter creates a gin router with all DBaaS CRUD handlers and
// a fresh profile backed by a temporary directory.
func setupDBaaSRouter(t *testing.T) *gin.Engine {
	t.Helper()

	dir := t.TempDir()
	_ = profile.New(dir)
	t.Setenv(profile.ConfigDirEnvKey, dir)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.GET("/dbaas", NewListDBaaSHandler())
	router.POST("/dbaas", NewCreateDBaaSHandler())
	router.GET("/dbaas/:id", NewGetDBaaSHandler())
	router.PUT("/dbaas/:id", NewUpdateDBaaSHandler())
	router.DELETE("/dbaas/:id", NewDeleteDBaaSHandler())
	return router
}

func TestDBaaSCRUD(t *testing.T) {
	router := setupDBaaSRouter(t)

	// List – initially empty
	t.Run("list empty", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/dbaas", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code)
		var list []dto.DBaaSResponse
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list))
		require.Empty(t, list)
	})

	// Create
	var createdID string
	t.Run("create service", func(t *testing.T) {
		body, err := json.Marshal(dto.DBaaSRequest{
			ID:       "dbs-test-1",
			Name:     "Test PingCAP",
			Provider: "pingcap_cloud",
			APIKey:   "sk-create-test",
		})
		require.NoError(t, err)

		req := httptest.NewRequest(http.MethodPost, "/dbaas", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String())
		var resp dto.DBaaSResponse
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
		require.Equal(t, "dbs-test-1", resp.ID)
		require.Equal(t, "Test PingCAP", resp.Name)
		require.Equal(t, "pingcap_cloud", resp.Provider)
		createdID = resp.ID
	})

	// Get by ID
	t.Run("get service", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/dbaas/"+createdID, nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code)
		var resp dto.DBaaSResponse
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
		require.Equal(t, createdID, resp.ID)
		require.Equal(t, "Test PingCAP", resp.Name)
	})

	// List – should have one entry
	t.Run("list after create", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/dbaas", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code)
		var list []dto.DBaaSResponse
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list))
		require.Len(t, list, 1)
		require.Equal(t, createdID, list[0].ID)
	})

	// Update
	t.Run("update service", func(t *testing.T) {
		body, err := json.Marshal(dto.DBaaSRequest{
			Name:     "Updated PingCAP",
			Provider: "pingcap_cloud",
			APIKey:   "sk-updated-key",
		})
		require.NoError(t, err)

		req := httptest.NewRequest(http.MethodPut, "/dbaas/"+createdID, bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
		var resp dto.DBaaSResponse
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
		require.Equal(t, "Updated PingCAP", resp.Name)
	})

	// Delete
	t.Run("delete service", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodDelete, "/dbaas/"+createdID, nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code)

		var body map[string]string
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
		require.Equal(t, "deleted", body["status"])
	})

	// List – empty again
	t.Run("list after delete", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/dbaas", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code)
		var list []dto.DBaaSResponse
		require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list))
		require.Empty(t, list)
	})
}

func TestDBaaSHandler_NotFound(t *testing.T) {
	router := setupDBaaSRouter(t)

	t.Run("get non-existent", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/dbaas/nonexistent", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusNotFound, rec.Code)
	})

	t.Run("delete non-existent", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodDelete, "/dbaas/nonexistent", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusNotFound, rec.Code)
	})

	t.Run("update non-existent", func(t *testing.T) {
		body, err := json.Marshal(dto.DBaaSRequest{
			Provider: "tidb_cloud",
		})
		require.NoError(t, err)

		req := httptest.NewRequest(http.MethodPut, "/dbaas/nonexistent", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusBadRequest, rec.Code)
	})
}

func TestDBaaSHandler_InvalidRequest(t *testing.T) {
	router := setupDBaaSRouter(t)

	t.Run("create with invalid JSON", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodPost, "/dbaas", bytes.NewReader([]byte("not json")))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusBadRequest, rec.Code)
	})

	t.Run("create with invalid provider", func(t *testing.T) {
		body, err := json.Marshal(dto.DBaaSRequest{
			ID:       "dbs-bad-provider",
			Provider: "invalid_provider",
		})
		require.NoError(t, err)

		req := httptest.NewRequest(http.MethodPost, "/dbaas", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusBadRequest, rec.Code)
	})

	t.Run("create duplicate ID", func(t *testing.T) {
		body, err := json.Marshal(dto.DBaaSRequest{
			ID:       "dbs-dup",
			Name:     "First",
			Provider: "tidb_cloud",
		})
		require.NoError(t, err)

		req := httptest.NewRequest(http.MethodPost, "/dbaas", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		require.Equal(t, http.StatusCreated, rec.Code)

		// Try to create again with same ID
		req = httptest.NewRequest(http.MethodPost, "/dbaas", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		rec = httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		require.Equal(t, http.StatusBadRequest, rec.Code)
	})
}
