package handlers

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

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

	"{{GO_MODULE}}/pkg/logging"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/domain"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/port"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/service"
)

// mockExampleRepository implements port.ExampleRepository for testing.
type mockExampleRepository struct {
	mu       sync.RWMutex
	examples map[domain.ExampleID]*domain.Example
}

var _ port.ExampleRepository = (*mockExampleRepository)(nil)

func newMockExampleRepository() *mockExampleRepository {
	return &mockExampleRepository{
		examples: make(map[domain.ExampleID]*domain.Example),
	}
}

func (m *mockExampleRepository) List(ctx context.Context) ([]domain.Example, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	result := make([]domain.Example, 0, len(m.examples))
	for _, e := range m.examples {
		result = append(result, *e)
	}
	return result, nil
}

func (m *mockExampleRepository) Get(ctx context.Context, id domain.ExampleID) (*domain.Example, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	e, ok := m.examples[id]
	if !ok {
		return nil, domain.ErrExampleNotFound
	}
	copy := *e
	return &copy, nil
}

func (m *mockExampleRepository) Create(ctx context.Context, example *domain.Example) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	copy := *example
	m.examples[example.ID] = &copy
	return nil
}

func (m *mockExampleRepository) Update(ctx context.Context, example *domain.Example) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, ok := m.examples[example.ID]; !ok {
		return domain.ErrExampleNotFound
	}
	copy := *example
	m.examples[example.ID] = &copy
	return nil
}

func (m *mockExampleRepository) Delete(ctx context.Context, id domain.ExampleID) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, ok := m.examples[id]; !ok {
		return domain.ErrExampleNotFound
	}
	delete(m.examples, id)
	return nil
}

func (m *mockExampleRepository) ExistsByName(ctx context.Context, name string) (bool, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	for _, e := range m.examples {
		if e.Name == name {
			return true, nil
		}
	}
	return false, nil
}

func newTestHandler() (*Example, *mockExampleRepository) {
	repo := newMockExampleRepository()
	svc := service.NewExampleService(repo, logging.Nop())
	handler := NewExample(svc, logging.Nop())
	return handler, repo
}

func TestExample_List(t *testing.T) {
	handler, repo := newTestHandler()

	// Seed data
	ex, _ := domain.NewExample("test-id-1", "Test Example", "Description")
	_ = repo.Create(context.Background(), ex)

	r := chi.NewRouter()
	r.Get("/api/v1/examples", func(w http.ResponseWriter, r *http.Request) {
		if err := handler.List(w, r); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
	})

	req := httptest.NewRequest(http.MethodGet, "/api/v1/examples", 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"]
	if !ok {
		t.Fatal("expected 'data' field in response")
	}

	items, ok := data.([]any)
	if !ok {
		t.Fatal("expected 'data' to be an array")
	}

	if len(items) != 1 {
		t.Errorf("expected 1 item, got %d", len(items))
	}
}

func TestExample_Get(t *testing.T) {
	handler, repo := newTestHandler()

	// Seed data
	ex, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440000", "Test Example", "Description")
	_ = repo.Create(context.Background(), ex)

	tests := []struct {
		name       string
		id         string
		wantStatus int
	}{
		{
			name:       "valid uuid - found",
			id:         "550e8400-e29b-41d4-a716-446655440000",
			wantStatus: http.StatusOK,
		},
		{
			name:       "valid uuid - not found",
			id:         "550e8400-e29b-41d4-a716-446655440001",
			wantStatus: http.StatusNotFound,
		},
		{
			name:       "invalid uuid",
			id:         "not-a-uuid",
			wantStatus: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := chi.NewRouter()
			r.Get("/api/v1/examples/{id}", func(w http.ResponseWriter, r *http.Request) {
				if err := handler.Get(w, r); err != nil {
					// Map error to status for testing
					switch tt.wantStatus {
					case http.StatusNotFound:
						w.WriteHeader(http.StatusNotFound)
					case http.StatusBadRequest:
						w.WriteHeader(http.StatusBadRequest)
					default:
						w.WriteHeader(http.StatusInternalServerError)
					}
					return
				}
			})

			req := httptest.NewRequest(http.MethodGet, "/api/v1/examples/"+tt.id, nil)
			w := httptest.NewRecorder()
			r.ServeHTTP(w, req)

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

func TestExample_Create(t *testing.T) {
	handler, repo := newTestHandler()

	// Seed existing data for duplicate test
	ex, _ := domain.NewExample("existing-id", "Existing Name", "")
	_ = repo.Create(context.Background(), ex)

	tests := []struct {
		name       string
		body       any
		wantStatus int
	}{
		{
			name: "valid request",
			body: CreateRequest{
				Name:        "New Example",
				Description: "A test description",
			},
			wantStatus: http.StatusCreated,
		},
		{
			name:       "empty body",
			body:       nil,
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "duplicate name",
			body: CreateRequest{
				Name:        "Existing Name",
				Description: "Conflict",
			},
			wantStatus: http.StatusConflict,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := chi.NewRouter()
			r.Post("/api/v1/examples", func(w http.ResponseWriter, r *http.Request) {
				if err := handler.Create(w, r); err != nil {
					switch tt.wantStatus {
					case http.StatusBadRequest:
						w.WriteHeader(http.StatusBadRequest)
					case http.StatusConflict:
						w.WriteHeader(http.StatusConflict)
					default:
						w.WriteHeader(http.StatusInternalServerError)
					}
					return
				}
			})

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

			req := httptest.NewRequest(http.MethodPost, "/api/v1/examples", bytes.NewReader(body))
			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", tt.wantStatus, w.Code)
			}
		})
	}
}

func TestExample_Delete(t *testing.T) {
	handler, repo := newTestHandler()

	// Seed data
	ex, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440000", "To Delete", "")
	_ = repo.Create(context.Background(), ex)

	tests := []struct {
		name       string
		id         string
		wantStatus int
	}{
		{
			name:       "existing example",
			id:         "550e8400-e29b-41d4-a716-446655440000",
			wantStatus: http.StatusNoContent,
		},
		{
			name:       "non-existent example",
			id:         "550e8400-e29b-41d4-a716-446655440001",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := chi.NewRouter()
			r.Delete("/api/v1/examples/{id}", func(w http.ResponseWriter, r *http.Request) {
				if err := handler.Delete(w, r); err != nil {
					if tt.wantStatus == http.StatusNotFound {
						w.WriteHeader(http.StatusNotFound)
					} else {
						w.WriteHeader(http.StatusBadRequest)
					}
					return
				}
			})

			req := httptest.NewRequest(http.MethodDelete, "/api/v1/examples/"+tt.id, nil)
			w := httptest.NewRecorder()
			r.ServeHTTP(w, req)

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

func TestExample_Update(t *testing.T) {
	handler, repo := newTestHandler()

	// Seed data
	ex1, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440000", "Example 1", "")
	_ = repo.Create(context.Background(), ex1)
	ex2, _ := domain.NewExample("550e8400-e29b-41d4-a716-446655440001", "Example 2", "")
	_ = repo.Create(context.Background(), ex2)

	tests := []struct {
		name       string
		id         string
		body       UpdateRequest
		wantStatus int
	}{
		{
			name: "valid update",
			id:   "550e8400-e29b-41d4-a716-446655440000",
			body: UpdateRequest{
				Name:        "Updated Name",
				Description: "Updated",
			},
			wantStatus: http.StatusOK,
		},
		{
			name: "name conflict",
			id:   "550e8400-e29b-41d4-a716-446655440000",
			body: UpdateRequest{
				Name:        "Example 2",
				Description: "Conflict",
			},
			wantStatus: http.StatusConflict,
		},
		{
			name: "not found",
			id:   "550e8400-e29b-41d4-a716-446655440099",
			body: UpdateRequest{
				Name:        "Whatever",
				Description: "",
			},
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := chi.NewRouter()
			r.Put("/api/v1/examples/{id}", func(w http.ResponseWriter, r *http.Request) {
				if err := handler.Update(w, r); err != nil {
					switch tt.wantStatus {
					case http.StatusNotFound:
						w.WriteHeader(http.StatusNotFound)
					case http.StatusConflict:
						w.WriteHeader(http.StatusConflict)
					default:
						w.WriteHeader(http.StatusBadRequest)
					}
					return
				}
			})

			body, _ := json.Marshal(tt.body)
			req := httptest.NewRequest(http.MethodPut, "/api/v1/examples/"+tt.id, bytes.NewReader(body))
			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", tt.wantStatus, w.Code)
			}
		})
	}
}
