package handlers

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

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

	"git.threesix.ai/jordan/slack5-1770529463/pkg/app"
	"git.threesix.ai/jordan/slack5-1770529463/pkg/logging"
	"git.threesix.ai/jordan/slack5-1770529463/services/preferences-api/internal/port"
	"git.threesix.ai/jordan/slack5-1770529463/services/preferences-api/internal/service"
)

// mockPreferenceRepository implements port.PreferenceRepository for testing.
type mockPreferenceRepository struct {
	mu    sync.RWMutex
	prefs map[string]map[string]string
}

var _ port.PreferenceRepository = (*mockPreferenceRepository)(nil)

func newMockPreferenceRepository() *mockPreferenceRepository {
	return &mockPreferenceRepository{
		prefs: make(map[string]map[string]string),
	}
}

func (m *mockPreferenceRepository) GetByUserID(ctx context.Context, userID string) ([]port.PreferenceRow, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	userPrefs, ok := m.prefs[userID]
	if !ok {
		return nil, nil
	}

	rows := make([]port.PreferenceRow, 0, len(userPrefs))
	for k, v := range userPrefs {
		rows = append(rows, port.PreferenceRow{
			UserID: userID,
			Key:    k,
			Value:  v,
		})
	}
	return rows, nil
}

func (m *mockPreferenceRepository) Upsert(ctx context.Context, userID string, key string, value string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if m.prefs[userID] == nil {
		m.prefs[userID] = make(map[string]string)
	}
	m.prefs[userID][key] = value
	return nil
}

func newTestPreferenceHandler() (*Preference, *mockPreferenceRepository) {
	repo := newMockPreferenceRepository()
	svc := service.NewPreferenceService(repo, logging.Nop())
	handler := NewPreference(svc, logging.Nop())
	return handler, repo
}

func TestPreference_GetPreferences(t *testing.T) {
	handler, repo := newTestPreferenceHandler()
	validUserID := "550e8400-e29b-41d4-a716-446655440000"

	t.Run("returns defaults for new user", func(t *testing.T) {
		r := chi.NewRouter()
		r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))

		req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+validUserID, nil)
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("expected status 200, got %d", w.Code)
		}

		var resp map[string]any
		if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}

		data, ok := resp["data"].(map[string]any)
		if !ok {
			t.Fatal("expected 'data' field as object in response")
		}

		if data["user_id"] != validUserID {
			t.Errorf("expected user_id %s, got %v", validUserID, data["user_id"])
		}

		prefs, ok := data["preferences"].(map[string]any)
		if !ok {
			t.Fatal("expected 'preferences' field as object in data")
		}

		if prefs["theme"] != "system" {
			t.Errorf("expected default theme 'system', got %v", prefs["theme"])
		}
		if prefs["language"] != "en" {
			t.Errorf("expected default language 'en', got %v", prefs["language"])
		}
		if prefs["notifications_enabled"] != true {
			t.Errorf("expected default notifications_enabled true, got %v", prefs["notifications_enabled"])
		}
	})

	t.Run("returns stored preferences merged with defaults", func(t *testing.T) {
		_ = repo.Upsert(context.Background(), validUserID, "theme", "dark")

		r := chi.NewRouter()
		r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))

		req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+validUserID, nil)
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("expected status 200, got %d", w.Code)
		}

		var resp map[string]any
		_ = json.NewDecoder(w.Body).Decode(&resp)
		data := resp["data"].(map[string]any)
		prefs := data["preferences"].(map[string]any)

		if prefs["theme"] != "dark" {
			t.Errorf("expected theme 'dark', got %v", prefs["theme"])
		}
	})

	t.Run("returns 400 for invalid user_id", func(t *testing.T) {
		r := chi.NewRouter()
		r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))

		req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/not-a-uuid", nil)
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusBadRequest {
			t.Errorf("expected status 400, got %d", w.Code)
		}
	})

	t.Run("response has meta field", func(t *testing.T) {
		r := chi.NewRouter()
		r.Get("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.GetPreferences))

		req := httptest.NewRequest(http.MethodGet, "/api/preferences-api/preferences/"+validUserID, nil)
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		var resp map[string]any
		_ = json.NewDecoder(w.Body).Decode(&resp)

		if _, ok := resp["meta"]; !ok {
			t.Error("expected 'meta' field in response")
		}
	})
}

func TestPreference_UpdatePreferences(t *testing.T) {
	handler, _ := newTestPreferenceHandler()
	validUserID := "550e8400-e29b-41d4-a716-446655440000"

	t.Run("updates preferences successfully", func(t *testing.T) {
		r := chi.NewRouter()
		r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))

		body, _ := json.Marshal(UpdatePreferencesRequest{
			Preferences: map[string]any{
				"theme":    "dark",
				"language": "fr",
			},
		})

		req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("expected status 200, got %d; body: %s", w.Code, w.Body.String())
		}

		var resp map[string]any
		_ = json.NewDecoder(w.Body).Decode(&resp)
		data := resp["data"].(map[string]any)
		prefs := data["preferences"].(map[string]any)

		if prefs["theme"] != "dark" {
			t.Errorf("expected theme 'dark', got %v", prefs["theme"])
		}
		if prefs["language"] != "fr" {
			t.Errorf("expected language 'fr', got %v", prefs["language"])
		}
		// Default should still be present
		if prefs["notifications_enabled"] != true {
			t.Errorf("expected notifications_enabled true, got %v", prefs["notifications_enabled"])
		}
	})

	t.Run("returns 400 for unknown key", func(t *testing.T) {
		r := chi.NewRouter()
		r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))

		body, _ := json.Marshal(UpdatePreferencesRequest{
			Preferences: map[string]any{
				"font_size": "large",
			},
		})

		req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusBadRequest {
			t.Errorf("expected status 400, got %d", w.Code)
		}
	})

	t.Run("returns 400 for invalid value", func(t *testing.T) {
		r := chi.NewRouter()
		r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))

		body, _ := json.Marshal(UpdatePreferencesRequest{
			Preferences: map[string]any{
				"theme": "neon",
			},
		})

		req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusBadRequest {
			t.Errorf("expected status 400, got %d", w.Code)
		}
	})

	t.Run("returns 400 for invalid user_id", func(t *testing.T) {
		r := chi.NewRouter()
		r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))

		body, _ := json.Marshal(UpdatePreferencesRequest{
			Preferences: map[string]any{
				"theme": "dark",
			},
		})

		req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/bad-id", bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusBadRequest {
			t.Errorf("expected status 400, got %d", w.Code)
		}
	})

	t.Run("handles boolean notifications_enabled", func(t *testing.T) {
		r := chi.NewRouter()
		r.Put("/api/preferences-api/preferences/{user_id}", app.Wrap(handler.UpdatePreferences))

		body, _ := json.Marshal(UpdatePreferencesRequest{
			Preferences: map[string]any{
				"notifications_enabled": false,
			},
		})

		req := httptest.NewRequest(http.MethodPut, "/api/preferences-api/preferences/"+validUserID, bytes.NewReader(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusOK {
			t.Errorf("expected status 200, got %d; body: %s", w.Code, w.Body.String())
		}

		var resp map[string]any
		_ = json.NewDecoder(w.Body).Decode(&resp)
		data := resp["data"].(map[string]any)
		prefs := data["preferences"].(map[string]any)

		if prefs["notifications_enabled"] != false {
			t.Errorf("expected notifications_enabled false, got %v", prefs["notifications_enabled"])
		}
	})
}
