package handlers

import (
	"context"
	"sync"
	"time"

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

// mockOperationRepo is a mock implementation of port.OperationRepository for testing.
type mockOperationRepo struct {
	mu         sync.Mutex
	operations map[string]*domain.Operation
}

func newMockOperationRepo() *mockOperationRepo {
	return &mockOperationRepo{
		operations: make(map[string]*domain.Operation),
	}
}

func (m *mockOperationRepo) Create(_ context.Context, op *domain.Operation) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.operations[op.ID] = op
	return nil
}

func (m *mockOperationRepo) Update(_ context.Context, op *domain.Operation) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.operations[op.ID] = op
	return nil
}

func (m *mockOperationRepo) Get(_ context.Context, id string) (*domain.Operation, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	op, ok := m.operations[id]
	if !ok {
		return nil, domain.ErrOperationNotFound
	}
	return op, nil
}

func (m *mockOperationRepo) GetByCommitSHA(_ context.Context, projectID, sha string) (*domain.Operation, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	for _, op := range m.operations {
		if op.ProjectID == projectID && op.CommitSHA == sha {
			return op, nil
		}
	}
	return nil, domain.ErrOperationNotFound
}

func (m *mockOperationRepo) List(_ context.Context, filter domain.OperationFilters) ([]*domain.Operation, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var result []*domain.Operation
	for _, op := range m.operations {
		if filter.ProjectID != "" && op.ProjectID != filter.ProjectID {
			continue
		}
		result = append(result, op)
	}
	return result, nil
}

func (m *mockOperationRepo) AddStep(_ context.Context, operationID string, step domain.OperationStep) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	op, ok := m.operations[operationID]
	if !ok {
		return domain.ErrOperationNotFound
	}
	op.Steps = append(op.Steps, step)
	return nil
}

func (m *mockOperationRepo) UpdateStep(_ context.Context, operationID string, step domain.OperationStep) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	op, ok := m.operations[operationID]
	if !ok {
		return domain.ErrOperationNotFound
	}
	for i, s := range op.Steps {
		if s.Name == step.Name {
			op.Steps[i] = step
			return nil
		}
	}
	return nil
}

func (m *mockOperationRepo) Complete(_ context.Context, operationID string, status domain.OperationStatus, output map[string]any, errMsg, errDetail string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	op, ok := m.operations[operationID]
	if !ok {
		return domain.ErrOperationNotFound
	}
	op.Status = status
	now := time.Now()
	op.CompletedAt = &now
	op.DurationMs = now.Sub(op.StartedAt).Milliseconds()
	op.Output = output
	op.Error = errMsg
	op.ErrorDetail = errDetail
	return nil
}

func (m *mockOperationRepo) SetCommitSHA(_ context.Context, operationID, sha string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	op, ok := m.operations[operationID]
	if !ok {
		return domain.ErrOperationNotFound
	}
	op.CommitSHA = sha
	return nil
}

func (m *mockOperationRepo) SetTriggeredBy(_ context.Context, operationID, parentID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	op, ok := m.operations[operationID]
	if !ok {
		return domain.ErrOperationNotFound
	}
	op.TriggeredBy = parentID
	return nil
}

func (m *mockOperationRepo) DeleteOlderThan(_ context.Context, _ time.Time) (int64, error) {
	return 0, nil
}

// count returns the number of operations stored.
func (m *mockOperationRepo) count() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.operations)
}
