package handlers

import (
	"bytes"
	"context"
	"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/port"
	"github.com/orchard9/rdev/internal/service"
)

func TestComponentsHandler_SetOperationService(t *testing.T) {
	h := NewComponentsHandler(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)
		h.SetOperationService(nil)
		if h.operationService != opSvc {
			t.Error("nil should not clear operation service")
		}
	})
}

func TestComponentsHandler_AddTracksOperation(t *testing.T) {
	opRepo := newMockOperationRepo()
	opSvc := service.NewOperationService(opRepo)

	mock := &mockComponentService{
		addComponent: func(ctx context.Context, projectID string, req port.AddComponentRequest) (*domain.Component, error) {
			return &domain.Component{
				Type:         domain.ComponentTypeService,
				Name:         "auth-api",
				Path:         "services/auth-api",
				Port:         8001,
				Template:     "service",
				Dependencies: []string{},
			}, nil
		},
	}

	handler := NewComponentsHandler(mock)
	handler.SetOperationService(opSvc)

	body, _ := json.Marshal(AddComponentRequest{Type: "service", Name: "auth-api"})
	req := httptest.NewRequest(http.MethodPost, "/projects/my-project/components", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Request-ID", "req-456")

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "my-project")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	rec := httptest.NewRecorder()
	handler.Add(rec, req)

	if rec.Code != http.StatusCreated {
		t.Fatalf("expected status 201, got %d. Body: %s", rec.Code, rec.Body.String())
	}

	// Verify operation was created and completed
	if opRepo.count() != 1 {
		t.Fatalf("expected 1 operation, got %d", opRepo.count())
	}

	// Verify operation status is completed
	for _, op := range opRepo.operations {
		if op.Status != domain.OperationStatusCompleted {
			t.Errorf("expected operation status completed, got %s", op.Status)
		}
		if op.ProjectID != "my-project" {
			t.Errorf("expected project_id my-project, got %s", op.ProjectID)
		}
		if op.Type != domain.OperationTypeComponentAdd {
			t.Errorf("expected operation type component.add, got %s", op.Type)
		}
		if op.RequestID != "req-456" {
			t.Errorf("expected request_id req-456, got %s", op.RequestID)
		}
	}

	// Verify response contains operation_id
	var resp struct {
		Data map[string]any `json:"data"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal response: %v", err)
	}
	opID, ok := resp.Data["operation_id"]
	if !ok {
		t.Error("response missing operation_id field")
	}
	if opID == "" {
		t.Error("operation_id should not be empty")
	}
}

func TestComponentsHandler_AddFailsOperationOnError(t *testing.T) {
	opRepo := newMockOperationRepo()
	opSvc := service.NewOperationService(opRepo)

	mock := &mockComponentService{
		addComponent: func(ctx context.Context, projectID string, req port.AddComponentRequest) (*domain.Component, error) {
			return nil, domain.ErrInvalidComponentType
		},
	}

	handler := NewComponentsHandler(mock)
	handler.SetOperationService(opSvc)

	body, _ := json.Marshal(AddComponentRequest{Type: "invalid", Name: "auth-api"})
	req := httptest.NewRequest(http.MethodPost, "/projects/my-project/components", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	rctx := chi.NewRouteContext()
	rctx.URLParams.Add("id", "my-project")
	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))

	rec := httptest.NewRecorder()
	handler.Add(rec, req)

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

	// Operation should be created and marked as failed
	if opRepo.count() != 1 {
		t.Fatalf("expected 1 operation, got %d", opRepo.count())
	}

	for _, op := range opRepo.operations {
		if op.Status != domain.OperationStatusFailed {
			t.Errorf("expected operation status failed, got %s", op.Status)
		}
		if op.Error == "" {
			t.Error("expected error message on failed operation")
		}
	}
}
