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-1770606136/pkg/httperror"
	"git.threesix.ai/jordan/slack5-1770606136/pkg/logging"
	"git.threesix.ai/jordan/slack5-1770606136/services/preferences-api/internal/domain"
	"git.threesix.ai/jordan/slack5-1770606136/services/preferences-api/internal/port"
	"git.threesix.ai/jordan/slack5-1770606136/services/preferences-api/internal/service"
)

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

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

func newMockPrefRepository() *mockPreferenceRepository {
	return &mockPreferenceRepository{
		prefs: make(map[string]*domain.UserPreferences),
	}
}

func (m *mockPreferenceRepository) Get(ctx context.Context, userID string) (*domain.UserPreferences, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	p, ok := m.prefs[userID]
	if !ok {
		return nil, nil
	}
	cp := *p
	cpPrefs := make(map[string]any)
	for k, v := range p.Preferences {
		cpPrefs[k] = v
	}
	cp.Preferences = cpPrefs
	return &cp, nil
}

func (m *mockPreferenceRepository) Upsert(ctx context.Context, prefs *domain.UserPreferences) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	cp := *prefs
	cpPrefs := make(map[string]any)
	for k, v := range prefs.Preferences {
		cpPrefs[k] = v
	}
	cp.Preferences = cpPrefs
	m.prefs[prefs.UserID] = &cp
	return nil
}

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

func TestPreference_Get(t *testing.T) {
	handler, repo := newTestPreferenceHandler()

	// Seed data
	repo.prefs["550e8400-e29b-41d4-a716-446655440000"] = &domain.UserPreferences{
		UserID:      "550e8400-e29b-41d4-a716-446655440000",
		Preferences: map[string]any{"theme": "dark"},
	}

	tests := []struct {
		name       string
		userID     string
		wantStatus int
		checkBody  func(t *testing.T, body map[string]any)
	}{
		{
			name:       "returns existing preferences",
			userID:     "550e8400-e29b-41d4-a716-446655440000",
			wantStatus: http.StatusOK,
			checkBody: func(t *testing.T, body map[string]any) {
				data, ok := body["data"].(map[string]any)
				if !ok {
					t.Fatal("expected 'data' field in response")
				}
				prefs, ok := data["preferences"].(map[string]any)
				if !ok {
					t.Fatal("expected 'preferences' field in data")
				}
				if prefs["theme"] != "dark" {
					t.Errorf("expected theme 'dark', got '%v'", prefs["theme"])
				}
			},
		},
		{
			name:       "returns empty preferences for unknown user",
			userID:     "550e8400-e29b-41d4-a716-446655440001",
			wantStatus: http.StatusOK,
			checkBody: func(t *testing.T, body map[string]any) {
				data, ok := body["data"].(map[string]any)
				if !ok {
					t.Fatal("expected 'data' field in response")
				}
				prefs, ok := data["preferences"].(map[string]any)
				if !ok {
					t.Fatal("expected 'preferences' field in data")
				}
				if len(prefs) != 0 {
					t.Errorf("expected empty preferences, got %v", prefs)
				}
			},
		},
		{
			name:       "returns 400 for invalid UUID",
			userID:     "not-a-uuid",
			wantStatus: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := chi.NewRouter()
			r.Get("/preferences/{user_id}", func(w http.ResponseWriter, r *http.Request) {
				if err := handler.Get(w, r); err != nil {
					// Write error status for test verification
					httpErr, ok := err.(*httperror.HTTPError)
					if ok {
						w.WriteHeader(httpErr.Status)
					} else {
						w.WriteHeader(http.StatusInternalServerError)
					}
					return
				}
			})

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

			if w.Code != tt.wantStatus {
				t.Errorf("expected status %d, got %d", tt.wantStatus, w.Code)
			}

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

func TestPreference_Upsert(t *testing.T) {
	handler, _ := newTestPreferenceHandler()

	tests := []struct {
		name       string
		userID     string
		body       any
		wantStatus int
		checkBody  func(t *testing.T, body map[string]any)
	}{
		{
			name:   "creates preferences successfully",
			userID: "550e8400-e29b-41d4-a716-446655440000",
			body: UpdatePreferencesRequest{
				Preferences: map[string]any{
					"theme":    "dark",
					"language": "en",
				},
			},
			wantStatus: http.StatusOK,
			checkBody: func(t *testing.T, body map[string]any) {
				data, ok := body["data"].(map[string]any)
				if !ok {
					t.Fatal("expected 'data' field in response")
				}
				prefs := data["preferences"].(map[string]any)
				if prefs["theme"] != "dark" {
					t.Errorf("expected theme 'dark', got '%v'", prefs["theme"])
				}
				if data["user_id"] != "550e8400-e29b-41d4-a716-446655440000" {
					t.Errorf("expected user_id in response, got '%v'", data["user_id"])
				}
			},
		},
		{
			name:   "returns 400 for invalid theme",
			userID: "550e8400-e29b-41d4-a716-446655440000",
			body: UpdatePreferencesRequest{
				Preferences: map[string]any{
					"theme": "neon",
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:       "returns 400 for missing preferences field",
			userID:     "550e8400-e29b-41d4-a716-446655440000",
			body:       map[string]any{},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:       "returns 400 for invalid UUID",
			userID:     "not-a-uuid",
			body:       UpdatePreferencesRequest{Preferences: map[string]any{"theme": "dark"}},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:       "returns 400 for empty body",
			userID:     "550e8400-e29b-41d4-a716-446655440000",
			body:       nil,
			wantStatus: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := chi.NewRouter()
			r.Put("/preferences/{user_id}", func(w http.ResponseWriter, r *http.Request) {
				if err := handler.Upsert(w, r); err != nil {
					httpErr, ok := err.(*httperror.HTTPError)
					if ok {
						w.WriteHeader(httpErr.Status)
					} else {
						w.WriteHeader(http.StatusInternalServerError)
					}
					return
				}
			})

			var bodyBytes []byte
			if tt.body != nil {
				var err error
				bodyBytes, err = json.Marshal(tt.body)
				if err != nil {
					t.Fatalf("failed to marshal body: %v", err)
				}
			}

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

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

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