package service

import (
	"context"
	"errors"
	"testing"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/internal/sdlc"
)

// mockGitCommitter implements PodGitCommitter for testing.
type mockGitCommitter struct {
	result *GitCommitResult
}

func (m *mockGitCommitter) CommitAndPush(_ context.Context, _, _, _ string, _ bool) *GitCommitResult {
	if m.result != nil {
		return m.result
	}
	return &GitCommitResult{
		HasChanges:   true,
		CommitSHA:    "abc123",
		FilesChanged: []string{"main.go"},
		Pushed:       true,
	}
}

// mockAgentRegistry implements port.CodeAgentRegistry for testing.
type mockAgentRegistry struct {
	agent port.CodeAgent
}

func (r *mockAgentRegistry) Register(_ port.CodeAgent)                          {}
func (r *mockAgentRegistry) Get(_ domain.AgentProvider) port.CodeAgent          { return r.agent }
func (r *mockAgentRegistry) Default() port.CodeAgent                            { return r.agent }
func (r *mockAgentRegistry) DefaultProvider() domain.AgentProvider              { return "" }
func (r *mockAgentRegistry) SetDefault(_ domain.AgentProvider) error            { return nil }
func (r *mockAgentRegistry) Available() []domain.AgentProvider                  { return nil }
func (r *mockAgentRegistry) AvailableAgents(_ context.Context) []port.CodeAgent { return nil }
func (r *mockAgentRegistry) Count() int                                         { return 0 }

// mockCodeAgent implements port.CodeAgent for testing.
type mockCodeAgent struct {
	result *domain.AgentResult
	err    error
}

func (a *mockCodeAgent) Name() string                             { return "test-agent" }
func (a *mockCodeAgent) Provider() domain.AgentProvider           { return "test" }
func (a *mockCodeAgent) Cancel(_ context.Context, _ string) error { return nil }
func (a *mockCodeAgent) Capabilities() domain.AgentCapabilities   { return domain.AgentCapabilities{} }
func (a *mockCodeAgent) Available(_ context.Context) bool         { return true }
func (a *mockCodeAgent) Execute(_ context.Context, _ *domain.AgentRequest, handler domain.AgentEventHandler) (*domain.AgentResult, error) {
	if a.err != nil {
		return nil, a.err
	}
	if handler != nil {
		handler(domain.AgentEvent{
			Type:    domain.AgentEventOutput,
			Content: "agent output",
		})
	}
	if a.result != nil {
		return a.result, nil
	}
	return &domain.AgentResult{}, nil
}

func newTestOrchestrator(exec *mockSDLCExecutor, repo *mockProjectRepo, registry port.CodeAgentRegistry, committer PodGitCommitter) *SDLCOrchestratorService {
	sdlcSvc := NewSDLCService(exec, repo)
	return NewSDLCOrchestratorService(sdlcSvc, registry, committer, repo)
}

func TestOrchestrator_ExecuteAction_Idle(t *testing.T) {
	exec := &mockSDLCExecutor{
		getNextFn: func(_ context.Context, _, _ string) (*sdlc.Classification, error) {
			return &sdlc.Classification{
				Action:  sdlc.ActionIdle,
				Feature: "auth",
				Message: "Nothing to do",
			}, nil
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	svc := newTestOrchestrator(exec, repo, nil, nil)

	result, err := svc.ExecuteAction(context.Background(), "proj", &ExecuteRequest{Feature: "auth"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !result.Success {
		t.Error("expected success")
	}
	if result.Action != sdlc.ActionIdle {
		t.Errorf("expected idle action, got %s", result.Action)
	}
	if result.Output != "Nothing to do" {
		t.Errorf("expected 'Nothing to do', got %s", result.Output)
	}
}

func TestOrchestrator_ExecuteAction_Transition(t *testing.T) {
	var transitioned bool
	exec := &mockSDLCExecutor{
		getNextFn: func(_ context.Context, _, _ string) (*sdlc.Classification, error) {
			if transitioned {
				return &sdlc.Classification{Action: sdlc.ActionIdle, Feature: "auth"}, nil
			}
			return &sdlc.Classification{
				Action:       sdlc.ActionTransition,
				Feature:      "auth",
				TransitionTo: sdlc.PhaseSpecified,
			}, nil
		},
		transitionFeatureFn: func(_ context.Context, _, _ string, _ sdlc.FeaturePhase) error {
			transitioned = true
			return nil
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	svc := newTestOrchestrator(exec, repo, nil, nil)

	result, err := svc.ExecuteAction(context.Background(), "proj", &ExecuteRequest{Feature: "auth"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !result.Success {
		t.Error("expected success")
	}
	if result.Action != sdlc.ActionTransition {
		t.Errorf("expected transition action, got %s", result.Action)
	}
	if !transitioned {
		t.Error("expected transition to have been called")
	}
}

func TestOrchestrator_ExecuteAction_AgentAction(t *testing.T) {
	exec := &mockSDLCExecutor{
		getNextFn: func(_ context.Context, _, _ string) (*sdlc.Classification, error) {
			return &sdlc.Classification{
				Action:      sdlc.ActionCreateSpec,
				Feature:     "auth",
				NextCommand: "create spec",
			}, nil
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	agent := &mockCodeAgent{}
	registry := &mockAgentRegistry{agent: agent}
	svc := newTestOrchestrator(exec, repo, registry, nil)

	result, err := svc.ExecuteAction(context.Background(), "proj", &ExecuteRequest{Feature: "auth"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !result.Success {
		t.Error("expected success")
	}
	if result.Output != "agent output" {
		t.Errorf("expected 'agent output', got %s", result.Output)
	}
}

func TestOrchestrator_ExecuteAction_NoAgent(t *testing.T) {
	exec := &mockSDLCExecutor{
		getNextFn: func(_ context.Context, _, _ string) (*sdlc.Classification, error) {
			return &sdlc.Classification{
				Action:  sdlc.ActionCreateSpec,
				Feature: "auth",
			}, nil
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	registry := &mockAgentRegistry{agent: nil}
	svc := newTestOrchestrator(exec, repo, registry, nil)

	result, err := svc.ExecuteAction(context.Background(), "proj", &ExecuteRequest{Feature: "auth"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result.Success {
		t.Error("expected failure (no agent)")
	}
	if result.Error == "" {
		t.Error("expected error message")
	}
}

func TestOrchestrator_ExecuteAction_ClassifyError(t *testing.T) {
	exec := &mockSDLCExecutor{
		getNextFn: func(_ context.Context, _, _ string) (*sdlc.Classification, error) {
			return nil, sdlc.ErrFeatureNotFound
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	svc := newTestOrchestrator(exec, repo, nil, nil)

	_, err := svc.ExecuteAction(context.Background(), "proj", &ExecuteRequest{Feature: "auth"})
	if !errors.Is(err, sdlc.ErrFeatureNotFound) {
		t.Errorf("expected ErrFeatureNotFound, got %v", err)
	}
}

func TestOrchestrator_ResolveBlocker(t *testing.T) {
	var unblocked bool
	exec := &mockSDLCExecutor{
		unblockFeatureFn: func(_ context.Context, _, _ string) error {
			unblocked = true
			return nil
		},
		getNextFn: func(_ context.Context, _, _ string) (*sdlc.Classification, error) {
			return &sdlc.Classification{
				Action:  sdlc.ActionIdle,
				Feature: "auth",
			}, nil
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	svc := newTestOrchestrator(exec, repo, nil, nil)

	result, err := svc.ResolveBlocker(context.Background(), "proj", &ResolveRequest{Feature: "auth"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !result.Success {
		t.Error("expected success")
	}
	if !unblocked {
		t.Error("expected unblock to have been called")
	}
	if result.Next == nil {
		t.Error("expected next classification")
	}
}

func TestOrchestrator_ResolveBlocker_Error(t *testing.T) {
	exec := &mockSDLCExecutor{
		unblockFeatureFn: func(_ context.Context, _, _ string) error {
			return sdlc.ErrFeatureNotFound
		},
	}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	svc := newTestOrchestrator(exec, repo, nil, nil)

	_, err := svc.ResolveBlocker(context.Background(), "proj", &ResolveRequest{Feature: "auth"})
	if !errors.Is(err, sdlc.ErrFeatureNotFound) {
		t.Errorf("expected ErrFeatureNotFound, got %v", err)
	}
}

func TestOrchestrator_CommitChanges(t *testing.T) {
	exec := &mockSDLCExecutor{}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	committer := &mockGitCommitter{
		result: &GitCommitResult{
			HasChanges:   true,
			CommitSHA:    "def456",
			FilesChanged: []string{"auth.go", "auth_test.go"},
			Pushed:       true,
		},
	}
	svc := newTestOrchestrator(exec, repo, nil, committer)

	result, err := svc.CommitChanges(context.Background(), "proj", &CommitRequest{
		Feature: "auth",
		Message: "implement auth",
		Push:    true,
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if result.CommitSHA != "def456" {
		t.Errorf("expected SHA def456, got %s", result.CommitSHA)
	}
	if len(result.FilesChanged) != 2 {
		t.Errorf("expected 2 files changed, got %d", len(result.FilesChanged))
	}
	if !result.Pushed {
		t.Error("expected pushed")
	}
}

func TestOrchestrator_CommitChanges_NoCommitter(t *testing.T) {
	exec := &mockSDLCExecutor{}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	svc := newTestOrchestrator(exec, repo, nil, nil) // no git committer

	_, err := svc.CommitChanges(context.Background(), "proj", &CommitRequest{
		Feature: "auth",
		Message: "commit",
	})
	if err == nil {
		t.Fatal("expected error for nil committer")
	}
}

func TestOrchestrator_CommitChanges_ProjectNotFound(t *testing.T) {
	exec := &mockSDLCExecutor{}
	repo := newMockProjectRepo() // empty
	committer := &mockGitCommitter{}
	svc := newTestOrchestrator(exec, repo, nil, committer)

	_, err := svc.CommitChanges(context.Background(), "nonexistent", &CommitRequest{
		Feature: "auth",
		Message: "commit",
	})
	if !errors.Is(err, domain.ErrProjectNotFound) {
		t.Errorf("expected ErrProjectNotFound, got %v", err)
	}
}

func TestOrchestrator_CommitChanges_GitError(t *testing.T) {
	exec := &mockSDLCExecutor{}
	repo := newMockProjectRepo(&domain.Project{ID: "proj", PodName: "pod"})
	committer := &mockGitCommitter{
		result: &GitCommitResult{
			Error: errors.New("push failed"),
		},
	}
	svc := newTestOrchestrator(exec, repo, nil, committer)

	_, err := svc.CommitChanges(context.Background(), "proj", &CommitRequest{
		Feature: "auth",
		Message: "commit",
		Push:    true,
	})
	if err == nil {
		t.Fatal("expected error for git failure")
	}
}
