package handlers

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

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/service"
)

func TestProjectManagementHandler_NilService(t *testing.T) {
	h := NewProjectManagementHandler(nil)
	r := chi.NewRouter()
	r.Use(testAdminAuth)
	h.Mount(r)

	tests := []struct {
		name   string
		method string
		path   string
		body   string
	}{
		{"create", "POST", "/project", `{"name":"test"}`},
		{"list", "GET", "/project", ""},
		{"status", "GET", "/project/test", ""},
		{"delete", "DELETE", "/project/test", ""},
		{"list templates", "GET", "/templates", ""},
		{"list component templates", "GET", "/templates/components", ""},
		{"get template", "GET", "/templates/default", ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var req *http.Request
			if tt.body != "" {
				req = httptest.NewRequest(tt.method, tt.path, bytes.NewReader([]byte(tt.body)))
			} else {
				req = httptest.NewRequest(tt.method, tt.path, nil)
			}
			rec := httptest.NewRecorder()
			r.ServeHTTP(rec, req)

			if rec.Code != http.StatusInternalServerError {
				t.Errorf("%s: status = %d, want %d", tt.name, rec.Code, http.StatusInternalServerError)
			}
		})
	}
}

func TestProjectManagementHandler_CreateValidation(t *testing.T) {
	// With nil service, the handler returns 500 before reaching validation.
	// This tests that the nil check takes precedence.
	h := NewProjectManagementHandler(nil)
	r := chi.NewRouter()
	r.Use(testAdminAuth)
	h.Mount(r)

	t.Run("nil service returns 500 even with missing name", func(t *testing.T) {
		body, _ := json.Marshal(CreateRequest{Name: ""})
		req := httptest.NewRequest("POST", "/project", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		r.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})

	t.Run("nil service returns 500 even with invalid json", func(t *testing.T) {
		req := httptest.NewRequest("POST", "/project", bytes.NewReader([]byte("not json")))
		rec := httptest.NewRecorder()
		r.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})
}

// Test removed: logger field removed from handler

func TestProjectManagementHandler_SetOperationService(t *testing.T) {
	h := NewProjectManagementHandler(nil)

	t.Run("sets non-nil service", func(t *testing.T) {
		repo := newMockOperationRepo()
		opSvc := service.NewOperationService(repo)
		result := h.SetOperationService(opSvc)
		if h.operationService != opSvc {
			t.Error("expected operation service to be set")
		}
		if result != h {
			t.Error("expected fluent return of handler")
		}
	})

	t.Run("ignores nil service", func(t *testing.T) {
		repo := newMockOperationRepo()
		opSvc := service.NewOperationService(repo)
		h.SetOperationService(opSvc) // set first
		h.SetOperationService(nil)   // should not clear
		if h.operationService != opSvc {
			t.Error("nil should not clear operation service")
		}
	})
}

func TestProjectManagementHandler_CreateTracksOperation(t *testing.T) {
	// This test verifies that the Create handler starts and completes operations
	// when an operationService is configured. Since we can't easily mock
	// ProjectInfraService (concrete struct), we test that nil infraService
	// still doesn't panic when operationService is set.
	repo := newMockOperationRepo()
	opSvc := service.NewOperationService(repo)

	h := NewProjectManagementHandler(nil).
		SetOperationService(opSvc)

	r := chi.NewRouter()
	r.Use(testAdminAuth)
	h.Mount(r)

	body, _ := json.Marshal(CreateRequest{Name: "test-project"})
	req := httptest.NewRequest("POST", "/project", bytes.NewReader(body))
	req.Header.Set("X-Request-ID", "req-123")
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)

	// Handler returns 500 because infraService is nil, but operation should NOT
	// have been started because the nil-service check happens before operation tracking.
	if rec.Code != http.StatusInternalServerError {
		t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
	}

	// Operation should not be created because infraService nil check comes first
	if repo.count() != 0 {
		t.Errorf("expected no operations (nil service returns early), got %d", repo.count())
	}
}

func TestProjectManagementHandler_OperationIDInResponse(t *testing.T) {
	// Verify the response shape includes operation_id field when it would be set.
	// We test the response structure by examining what Create() writes.
	// Since we can't mock the concrete ProjectInfraService, this is a structural test
	// verifying the handler properly sets the operationService field.
	h := NewProjectManagementHandler(nil)
	if h.operationService != nil {
		t.Error("operationService should be nil by default")
	}

	repo := newMockOperationRepo()
	opSvc := service.NewOperationService(repo)
	h.SetOperationService(opSvc)

	if h.operationService == nil {
		t.Error("operationService should be set after SetOperationService")
	}
}

func TestProjectManagementHandler_OperationFailsOnError(t *testing.T) {
	// When operationService is set but create fails, the operation should be marked failed.
	// Since nil infraService returns 500 before reaching operation tracking, we verify
	// that operations are not leaked when the handler exits early.
	repo := newMockOperationRepo()
	opSvc := service.NewOperationService(repo)

	h := &ProjectManagementHandler{
		infraService:     nil, // will cause 500
		operationService: opSvc,
	}

	r := chi.NewRouter()
	r.Post("/project", h.Create)

	body, _ := json.Marshal(CreateRequest{Name: "fail-project"})
	req := httptest.NewRequest("POST", "/project", bytes.NewReader(body))
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)

	// The nil infraService check happens before operation tracking starts,
	// so no operation should exist
	if repo.count() != 0 {
		// If an operation was created, verify it was marked as failed
		for _, op := range repo.operations {
			if op.Status != domain.OperationStatusFailed {
				t.Errorf("expected operation status failed, got %s", op.Status)
			}
		}
	}
}
