package sdlc

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"gopkg.in/yaml.v3"
)

// Feature represents a feature under development, stored in manifest.yaml.
type Feature struct {
	Slug         string                     `yaml:"slug" json:"slug"`
	Title        string                     `yaml:"title" json:"title"`
	Created      time.Time                  `yaml:"created" json:"created"`
	Branch       string                     `yaml:"branch,omitempty" json:"branch,omitempty"`
	RoadmapRef   string                     `yaml:"roadmap_ref,omitempty" json:"roadmap_ref,omitempty"`
	Phase        FeaturePhase               `yaml:"phase" json:"phase"`
	PhaseHistory []PhaseTransition          `yaml:"phase_history" json:"phase_history"`
	Artifacts    map[ArtifactType]*Artifact `yaml:"artifacts" json:"artifacts"`
	Tasks        []Task                     `yaml:"tasks,omitempty" json:"tasks,omitempty"`
	Blockers     []string                   `yaml:"blockers,omitempty" json:"blockers,omitempty"`
	Dependencies Dependencies               `yaml:"dependencies,omitempty" json:"dependencies,omitempty"` //nolint:omitzero
}

// CreateFeature creates a new feature directory and manifest.
func CreateFeature(root, slug, title string) (*Feature, error) {
	if err := ValidateSlug(slug); err != nil {
		return nil, err
	}

	dir := FeatureDir(root, slug)
	if _, err := os.Stat(dir); err == nil {
		return nil, ErrFeatureExists
	}

	if err := os.MkdirAll(dir, 0o755); err != nil {
		return nil, fmt.Errorf("create feature directory: %w", err)
	}

	now := time.Now().UTC()
	f := &Feature{
		Slug:    slug,
		Title:   title,
		Created: now,
		Phase:   PhaseDraft,
		PhaseHistory: []PhaseTransition{
			{Phase: PhaseDraft, Entered: now},
		},
		Artifacts: map[ArtifactType]*Artifact{
			ArtifactSpec:      NewArtifact(ArtifactSpec),
			ArtifactDesign:    NewArtifact(ArtifactDesign),
			ArtifactTasks:     NewArtifact(ArtifactTasks),
			ArtifactQAPlan:    NewArtifact(ArtifactQAPlan),
			ArtifactReview:    NewArtifact(ArtifactReview),
			ArtifactAudit:     NewArtifact(ArtifactAudit),
			ArtifactQAResults: NewArtifact(ArtifactQAResults),
		},
	}

	if err := f.Save(root); err != nil {
		return nil, err
	}
	return f, nil
}

// LoadFeature reads a feature manifest from disk.
func LoadFeature(root, slug string) (*Feature, error) {
	path := ManifestPath(root, slug)
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, ErrFeatureNotFound
		}
		return nil, fmt.Errorf("read feature manifest: %w", err)
	}

	var f Feature
	if err := yaml.Unmarshal(data, &f); err != nil {
		return nil, fmt.Errorf("parse feature manifest: %w", err)
	}
	return &f, nil
}

// Save writes the feature manifest to disk.
func (f *Feature) Save(root string) error {
	data, err := yaml.Marshal(f)
	if err != nil {
		return fmt.Errorf("marshal feature manifest: %w", err)
	}

	path := ManifestPath(root, f.Slug)
	if err := os.WriteFile(path, data, 0o644); err != nil {
		return fmt.Errorf("write feature manifest: %w", err)
	}
	return nil
}

// ListFeatures returns all features found in .sdlc/features/.
func ListFeatures(root string) ([]*Feature, error) {
	dir := FeaturesDirPath(root)
	entries, err := os.ReadDir(dir)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, ErrNotInitialized
		}
		return nil, fmt.Errorf("read features directory: %w", err)
	}

	var features []*Feature
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}
		manifestPath := filepath.Join(dir, entry.Name(), ManifestFile)
		if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
			continue
		}
		f, err := LoadFeature(root, entry.Name())
		if err != nil {
			continue
		}
		features = append(features, f)
	}
	return features, nil
}

// CanTransitionTo checks if the feature can move to the target phase.
// It validates ordering, config allowances, required artifacts, and blockers.
func (f *Feature) CanTransitionTo(target FeaturePhase, cfg *Config) error {
	if !IsValidPhase(target) {
		return fmt.Errorf("%w: %s is not a valid phase", ErrInvalidPhase, target)
	}

	if cfg != nil && !cfg.IsPhaseEnabled(target) {
		return fmt.Errorf("%w: phase %s is not enabled", ErrInvalidTransition, target)
	}

	currentIdx := PhaseIndex(f.Phase)
	targetIdx := PhaseIndex(target)

	// Idempotent: if already in target phase, return success
	if targetIdx == currentIdx {
		return nil
	}

	// Reject backward transitions
	if targetIdx < currentIdx {
		return fmt.Errorf("%w: cannot move from %s to %s (backward)", ErrInvalidTransition, f.Phase, target)
	}

	// Only allow moving to the next phase (no skipping)
	if targetIdx != currentIdx+1 {
		return fmt.Errorf("%w: cannot skip from %s to %s", ErrInvalidTransition, f.Phase, target)
	}

	// Check required artifacts for the target phase are approved/passed
	if cfg != nil {
		required, ok := cfg.Phases.RequiredArtifacts[target]
		if ok {
			for _, artType := range required {
				art, exists := f.Artifacts[artType]
				if !exists {
					return fmt.Errorf("%w: missing required artifact %s to enter phase %s", ErrInvalidTransition, artType, target)
				}
				if art.Status != StatusApproved && art.Status != StatusPassed {
					return fmt.Errorf("%w: artifact %s is %s, must be approved or passed to enter %s", ErrInvalidTransition, artType, art.Status, target)
				}
			}
		}
	}

	// Check no blockers
	if len(f.Blockers) > 0 {
		return fmt.Errorf("%w: feature has %d blocker(s)", ErrInvalidTransition, len(f.Blockers))
	}

	return nil
}

// Transition moves the feature to the target phase, recording history.
func (f *Feature) Transition(target FeaturePhase) error {
	now := time.Now().UTC()

	// Close the current phase history entry
	if len(f.PhaseHistory) > 0 {
		f.PhaseHistory[len(f.PhaseHistory)-1].Exited = &now
	}

	f.Phase = target
	f.PhaseHistory = append(f.PhaseHistory, PhaseTransition{
		Phase:   target,
		Entered: now,
	})

	return nil
}

// AddBlocker adds a blocker reason.
func (f *Feature) AddBlocker(reason string) {
	f.Blockers = append(f.Blockers, reason)
}

// ClearBlockers removes all blockers.
func (f *Feature) ClearBlockers() {
	f.Blockers = nil
}

// IsBlocked returns true if the feature has any blockers.
func (f *Feature) IsBlocked() bool {
	return len(f.Blockers) > 0
}

// GetArtifact returns the artifact by type, or nil.
func (f *Feature) GetArtifact(t ArtifactType) *Artifact {
	if f.Artifacts == nil {
		return nil
	}
	return f.Artifacts[t]
}

// SetArtifact sets or creates an artifact entry.
func (f *Feature) SetArtifact(t ArtifactType, a *Artifact) {
	if f.Artifacts == nil {
		f.Artifacts = make(map[ArtifactType]*Artifact)
	}
	f.Artifacts[t] = a
}

// ArtifactFileExists checks if the artifact file exists on disk.
func (f *Feature) ArtifactFileExists(root string, artType ArtifactType) bool {
	path := ArtifactPath(root, f.Slug, artType)
	if path == "" {
		return false
	}
	_, err := os.Stat(path)
	return err == nil
}

// DeleteFeature removes a feature directory and all contents.
func DeleteFeature(root, slug string) error {
	dir := FeatureDir(root, slug)
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		return ErrFeatureNotFound
	}
	return os.RemoveAll(dir)
}

// ArchiveFeature moves a feature from features/ to archives/.
func ArchiveFeature(root, slug string) error {
	src := FeatureDir(root, slug)
	if _, err := os.Stat(src); os.IsNotExist(err) {
		return ErrFeatureNotFound
	}

	dst := filepath.Join(SDLCRoot(root), ArchivesDir, slug)
	if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
		return fmt.Errorf("create archives directory: %w", err)
	}
	return os.Rename(src, dst)
}

// UpdateTaskSummary refreshes the tasks artifact with current counts.
func (f *Feature) UpdateTaskSummary() {
	summary := SummarizeTasks(f.Tasks)
	if art := f.GetArtifact(ArtifactTasks); art != nil {
		art.Total = summary.Total
		art.Completed = summary.Completed
		art.InProgress = summary.InProgress
		art.Blocked = summary.Blocked
	}
}
