package service

import (
	"context"
	"errors"
	"testing"

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

// mockSDLCExecutor implements port.SDLCExecutor for testing.
type mockSDLCExecutor struct {
	getStateFn          func(ctx context.Context, podName string) (*sdlc.State, error)
	getNextFn           func(ctx context.Context, podName, feature string) (*sdlc.Classification, error)
	listFeaturesFn      func(ctx context.Context, podName string) ([]*sdlc.Feature, error)
	getFeatureFn        func(ctx context.Context, podName, slug string) (*sdlc.Feature, error)
	createFeatureFn     func(ctx context.Context, podName, slug, title string) (*sdlc.Feature, error)
	transitionFeatureFn func(ctx context.Context, podName, slug string, phase sdlc.FeaturePhase) error
	blockFeatureFn      func(ctx context.Context, podName, slug, reason string) error
	unblockFeatureFn    func(ctx context.Context, podName, slug string) error
	deleteFeatureFn     func(ctx context.Context, podName, slug string) error
	getArtifactStatusFn func(ctx context.Context, podName, slug string) (map[sdlc.ArtifactType]*sdlc.Artifact, error)
	approveArtifactFn   func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
	rejectArtifactFn    func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
	passArtifactFn      func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
	failArtifactFn      func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
	needsFixArtifactFn  func(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error
	listTasksFn         func(ctx context.Context, podName, slug string) ([]sdlc.Task, error)
	addTaskFn           func(ctx context.Context, podName, slug, title string) (*sdlc.Task, error)
	startTaskFn         func(ctx context.Context, podName, slug, taskID string) error
	completeTaskFn      func(ctx context.Context, podName, slug, taskID string) error
	blockTaskFn         func(ctx context.Context, podName, slug, taskID string) error
	queryBlockedFn      func(ctx context.Context, podName string) ([]port.BlockedInfo, error)
	queryReadyFn        func(ctx context.Context, podName string) ([]port.ReadyInfo, error)
	queryNeedsApprFn    func(ctx context.Context, podName string) ([]port.ApprovalInfo, error)
	createBranchFn      func(ctx context.Context, podName, slug string) (*sdlc.BranchManifest, error)
	getBranchStatusFn   func(ctx context.Context, podName, slug string) (*port.BranchStatus, error)
	syncBranchFn        func(ctx context.Context, podName, slug string) error
	mergeFeatureFn      func(ctx context.Context, podName, slug, strategy string) error
	archiveFeatureFn    func(ctx context.Context, podName, slug string) error
}

func (m *mockSDLCExecutor) GetState(ctx context.Context, podName string) (*sdlc.State, error) {
	if m.getStateFn != nil {
		return m.getStateFn(ctx, podName)
	}
	return &sdlc.State{Version: 1}, nil
}

func (m *mockSDLCExecutor) GetNext(ctx context.Context, podName, feature string) (*sdlc.Classification, error) {
	if m.getNextFn != nil {
		return m.getNextFn(ctx, podName, feature)
	}
	return &sdlc.Classification{Action: sdlc.ActionIdle}, nil
}

func (m *mockSDLCExecutor) ListFeatures(ctx context.Context, podName string) ([]*sdlc.Feature, error) {
	if m.listFeaturesFn != nil {
		return m.listFeaturesFn(ctx, podName)
	}
	return nil, nil
}

func (m *mockSDLCExecutor) GetFeature(ctx context.Context, podName, slug string) (*sdlc.Feature, error) {
	if m.getFeatureFn != nil {
		return m.getFeatureFn(ctx, podName, slug)
	}
	return &sdlc.Feature{Slug: slug}, nil
}

func (m *mockSDLCExecutor) CreateFeature(ctx context.Context, podName, slug, title string) (*sdlc.Feature, error) {
	if m.createFeatureFn != nil {
		return m.createFeatureFn(ctx, podName, slug, title)
	}
	return &sdlc.Feature{Slug: slug, Title: title}, nil
}

func (m *mockSDLCExecutor) TransitionFeature(ctx context.Context, podName, slug string, phase sdlc.FeaturePhase) error {
	if m.transitionFeatureFn != nil {
		return m.transitionFeatureFn(ctx, podName, slug, phase)
	}
	return nil
}

func (m *mockSDLCExecutor) BlockFeature(ctx context.Context, podName, slug, reason string) error {
	if m.blockFeatureFn != nil {
		return m.blockFeatureFn(ctx, podName, slug, reason)
	}
	return nil
}

func (m *mockSDLCExecutor) UnblockFeature(ctx context.Context, podName, slug string) error {
	if m.unblockFeatureFn != nil {
		return m.unblockFeatureFn(ctx, podName, slug)
	}
	return nil
}

func (m *mockSDLCExecutor) DeleteFeature(ctx context.Context, podName, slug string) error {
	if m.deleteFeatureFn != nil {
		return m.deleteFeatureFn(ctx, podName, slug)
	}
	return nil
}

func (m *mockSDLCExecutor) GetArtifactStatus(ctx context.Context, podName, slug string) (map[sdlc.ArtifactType]*sdlc.Artifact, error) {
	if m.getArtifactStatusFn != nil {
		return m.getArtifactStatusFn(ctx, podName, slug)
	}
	return nil, nil
}

func (m *mockSDLCExecutor) ApproveArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	if m.approveArtifactFn != nil {
		return m.approveArtifactFn(ctx, podName, slug, artType)
	}
	return nil
}

func (m *mockSDLCExecutor) RejectArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	if m.rejectArtifactFn != nil {
		return m.rejectArtifactFn(ctx, podName, slug, artType)
	}
	return nil
}

func (m *mockSDLCExecutor) PassArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	if m.passArtifactFn != nil {
		return m.passArtifactFn(ctx, podName, slug, artType)
	}
	return nil
}

func (m *mockSDLCExecutor) FailArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	if m.failArtifactFn != nil {
		return m.failArtifactFn(ctx, podName, slug, artType)
	}
	return nil
}

func (m *mockSDLCExecutor) NeedsFixArtifact(ctx context.Context, podName, slug string, artType sdlc.ArtifactType) error {
	if m.needsFixArtifactFn != nil {
		return m.needsFixArtifactFn(ctx, podName, slug, artType)
	}
	return nil
}

func (m *mockSDLCExecutor) ListTasks(ctx context.Context, podName, slug string) ([]sdlc.Task, error) {
	if m.listTasksFn != nil {
		return m.listTasksFn(ctx, podName, slug)
	}
	return nil, nil
}

func (m *mockSDLCExecutor) AddTask(ctx context.Context, podName, slug, title string) (*sdlc.Task, error) {
	if m.addTaskFn != nil {
		return m.addTaskFn(ctx, podName, slug, title)
	}
	return &sdlc.Task{ID: "task-001", Title: title}, nil
}

func (m *mockSDLCExecutor) StartTask(ctx context.Context, podName, slug, taskID string) error {
	if m.startTaskFn != nil {
		return m.startTaskFn(ctx, podName, slug, taskID)
	}
	return nil
}

func (m *mockSDLCExecutor) CompleteTask(ctx context.Context, podName, slug, taskID string) error {
	if m.completeTaskFn != nil {
		return m.completeTaskFn(ctx, podName, slug, taskID)
	}
	return nil
}

func (m *mockSDLCExecutor) BlockTask(ctx context.Context, podName, slug, taskID string) error {
	if m.blockTaskFn != nil {
		return m.blockTaskFn(ctx, podName, slug, taskID)
	}
	return nil
}

func (m *mockSDLCExecutor) QueryBlocked(ctx context.Context, podName string) ([]port.BlockedInfo, error) {
	if m.queryBlockedFn != nil {
		return m.queryBlockedFn(ctx, podName)
	}
	return nil, nil
}

func (m *mockSDLCExecutor) QueryReady(ctx context.Context, podName string) ([]port.ReadyInfo, error) {
	if m.queryReadyFn != nil {
		return m.queryReadyFn(ctx, podName)
	}
	return nil, nil
}

func (m *mockSDLCExecutor) QueryNeedsApproval(ctx context.Context, podName string) ([]port.ApprovalInfo, error) {
	if m.queryNeedsApprFn != nil {
		return m.queryNeedsApprFn(ctx, podName)
	}
	return nil, nil
}

func (m *mockSDLCExecutor) CreateBranch(ctx context.Context, podName, slug string) (*sdlc.BranchManifest, error) {
	if m.createBranchFn != nil {
		return m.createBranchFn(ctx, podName, slug)
	}
	return &sdlc.BranchManifest{Name: "feature/" + slug, Feature: slug}, nil
}

func (m *mockSDLCExecutor) GetBranchStatus(ctx context.Context, podName, slug string) (*port.BranchStatus, error) {
	if m.getBranchStatusFn != nil {
		return m.getBranchStatusFn(ctx, podName, slug)
	}
	return &port.BranchStatus{
		Branch:    &sdlc.BranchManifest{Name: "feature/" + slug, Feature: slug},
		Checklist: nil,
		Ready:     true,
	}, nil
}

func (m *mockSDLCExecutor) SyncBranch(ctx context.Context, podName, slug string) error {
	if m.syncBranchFn != nil {
		return m.syncBranchFn(ctx, podName, slug)
	}
	return nil
}

func (m *mockSDLCExecutor) MergeFeature(ctx context.Context, podName, slug, strategy string) error {
	if m.mergeFeatureFn != nil {
		return m.mergeFeatureFn(ctx, podName, slug, strategy)
	}
	return nil
}

func (m *mockSDLCExecutor) ArchiveFeature(ctx context.Context, podName, slug string) error {
	if m.archiveFeatureFn != nil {
		return m.archiveFeatureFn(ctx, podName, slug)
	}
	return nil
}

// mockProjectRepo implements port.ProjectRepository for testing.
type mockProjectRepo struct {
	projects map[domain.ProjectID]*domain.Project
}

func newMockProjectRepo(projects ...*domain.Project) *mockProjectRepo {
	m := &mockProjectRepo{projects: make(map[domain.ProjectID]*domain.Project)}
	for _, p := range projects {
		m.projects[p.ID] = p
	}
	return m
}

func (m *mockProjectRepo) Get(_ context.Context, id domain.ProjectID) (*domain.Project, error) {
	p, ok := m.projects[id]
	if !ok {
		return nil, domain.ErrProjectNotFound
	}
	return p, nil
}

func (m *mockProjectRepo) List(_ context.Context) ([]domain.Project, error) { return nil, nil }
func (m *mockProjectRepo) Exists(_ context.Context, id domain.ProjectID) (bool, error) {
	_, ok := m.projects[id]
	return ok, nil
}
func (m *mockProjectRepo) Register(_ context.Context, _ *domain.Project) error    { return nil }
func (m *mockProjectRepo) Unregister(_ context.Context, _ domain.ProjectID) error { return nil }
func (m *mockProjectRepo) RefreshStatus(_ context.Context) error                  { return nil }

func newTestService(exec port.SDLCExecutor, repo *mockProjectRepo) *SDLCService {
	return NewSDLCService(exec, repo)
}

func TestSDLCService_GetState(t *testing.T) {
	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{
		getStateFn: func(_ context.Context, podName string) (*sdlc.State, error) {
			if podName != "myproj-pod" {
				t.Errorf("expected podName myproj-pod, got %s", podName)
			}
			return &sdlc.State{Version: 1}, nil
		},
	}
	svc := newTestService(exec, repo)

	state, err := svc.GetState(context.Background(), "myproj")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if state.Version != 1 {
		t.Errorf("expected version 1, got %d", state.Version)
	}
}

func TestSDLCService_ProjectNotFound(t *testing.T) {
	repo := newMockProjectRepo() // empty
	exec := &mockSDLCExecutor{}
	svc := newTestService(exec, repo)

	_, err := svc.GetState(context.Background(), "nonexistent")
	if !errors.Is(err, domain.ErrProjectNotFound) {
		t.Errorf("expected ErrProjectNotFound, got %v", err)
	}
}

func TestSDLCService_TransitionFeature(t *testing.T) {
	var calledSlug string
	var calledPhase sdlc.FeaturePhase

	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{
		transitionFeatureFn: func(_ context.Context, _ string, slug string, phase sdlc.FeaturePhase) error {
			calledSlug = slug
			calledPhase = phase
			return nil
		},
	}
	svc := newTestService(exec, repo)

	err := svc.TransitionFeature(context.Background(), "myproj", "auth-flow", sdlc.PhaseSpecified)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if calledSlug != "auth-flow" {
		t.Errorf("expected slug auth-flow, got %s", calledSlug)
	}
	if calledPhase != sdlc.PhaseSpecified {
		t.Errorf("expected phase specified, got %s", calledPhase)
	}
}

func TestSDLCService_TransitionFeature_Error(t *testing.T) {
	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{
		transitionFeatureFn: func(_ context.Context, _, _ string, _ sdlc.FeaturePhase) error {
			return sdlc.ErrInvalidTransition
		},
	}
	svc := newTestService(exec, repo)

	err := svc.TransitionFeature(context.Background(), "myproj", "auth-flow", sdlc.PhaseReview)
	if !errors.Is(err, sdlc.ErrInvalidTransition) {
		t.Errorf("expected ErrInvalidTransition, got %v", err)
	}
}

func TestSDLCService_CreateFeature(t *testing.T) {
	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{}
	svc := newTestService(exec, repo)

	f, err := svc.CreateFeature(context.Background(), "myproj", "new-feature", "New Feature")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if f.Slug != "new-feature" {
		t.Errorf("expected slug new-feature, got %s", f.Slug)
	}
}

func TestSDLCService_ApproveArtifact(t *testing.T) {
	var calledArtType sdlc.ArtifactType
	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{
		approveArtifactFn: func(_ context.Context, _, _ string, artType sdlc.ArtifactType) error {
			calledArtType = artType
			return nil
		},
	}
	svc := newTestService(exec, repo)

	err := svc.ApproveArtifact(context.Background(), "myproj", "auth-flow", sdlc.ArtifactSpec)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if calledArtType != sdlc.ArtifactSpec {
		t.Errorf("expected artifact type spec, got %s", calledArtType)
	}
}

func TestSDLCService_QueryBlocked(t *testing.T) {
	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{
		queryBlockedFn: func(_ context.Context, _ string) ([]port.BlockedInfo, error) {
			return []port.BlockedInfo{
				{Slug: "auth", Phase: "implementation", Blockers: []string{"needs API key"}},
			}, nil
		},
	}
	svc := newTestService(exec, repo)

	blocked, err := svc.QueryBlocked(context.Background(), "myproj")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(blocked) != 1 {
		t.Fatalf("expected 1 blocked item, got %d", len(blocked))
	}
	if blocked[0].Slug != "auth" {
		t.Errorf("expected slug auth, got %s", blocked[0].Slug)
	}
}

func TestSDLCService_AddTask(t *testing.T) {
	repo := newMockProjectRepo(&domain.Project{
		ID:      "myproj",
		PodName: "myproj-pod",
	})
	exec := &mockSDLCExecutor{}
	svc := newTestService(exec, repo)

	task, err := svc.AddTask(context.Background(), "myproj", "auth-flow", "Add login form")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if task.Title != "Add login form" {
		t.Errorf("expected title 'Add login form', got %s", task.Title)
	}
}

func TestSDLCService_DualExecutor_UsesPodExecutor(t *testing.T) {
	// When project has a pod, should use pod executor
	repo := newMockProjectRepo(&domain.Project{
		ID:      "pod-project",
		PodName: "pod-project-pod",
	})
	podExec := &mockSDLCExecutor{
		getStateFn: func(_ context.Context, podName string) (*sdlc.State, error) {
			if podName != "pod-project-pod" {
				t.Errorf("expected podName 'pod-project-pod', got %s", podName)
			}
			return &sdlc.State{Version: 1}, nil
		},
	}
	workerExec := &mockSDLCExecutor{
		getStateFn: func(_ context.Context, _ string) (*sdlc.State, error) {
			t.Error("worker executor should not be called for project with pod")
			return nil, nil
		},
	}

	svc := NewSDLCServiceWithWorker(podExec, workerExec, repo, nil)

	state, err := svc.GetState(context.Background(), "pod-project")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if state.Version != 1 {
		t.Errorf("expected version 1, got %d", state.Version)
	}
}

func TestSDLCService_DualExecutor_FallbackToOldBehavior(t *testing.T) {
	// When no worker executor configured and project has no pod, should return error
	repo := newMockProjectRepo(&domain.Project{
		ID:      "no-pod-project",
		PodName: "", // No pod
	})
	podExec := &mockSDLCExecutor{}

	// Using NewSDLCService (single executor) should fail for project without pod
	svc := NewSDLCService(podExec, repo)

	_, err := svc.GetState(context.Background(), "no-pod-project")
	if err == nil {
		t.Error("expected error for project without pod when no worker executor")
	}
	if !errors.Is(err, domain.ErrProjectNotFound) {
		t.Errorf("expected ErrProjectNotFound, got %v", err)
	}
}
