package sdlc

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

	"gopkg.in/yaml.v3"
)

// BranchManifest tracks branch metadata for a feature.
type BranchManifest struct {
	Name          string     `yaml:"name" json:"name"`
	Feature       string     `yaml:"feature" json:"feature"`
	BaseBranch    string     `yaml:"base_branch" json:"base_branch"`
	CreatedAt     time.Time  `yaml:"created_at" json:"created_at"`
	LastSyncAt    *time.Time `yaml:"last_sync_at,omitempty" json:"last_sync_at,omitempty"`
	MergedAt      *time.Time `yaml:"merged_at,omitempty" json:"merged_at,omitempty"`
	MergeStrategy string     `yaml:"merge_strategy,omitempty" json:"merge_strategy,omitempty"`
}

// BranchPath returns the path to a branch manifest file.
func BranchPath(root, branchName string) string {
	return filepath.Join(root, SDLCDir, BranchesDir, branchName+".yaml")
}

// CreateBranch creates a new branch manifest for a feature.
// It validates the feature exists, is in PhasePlanned or later, and constructs the branch name from config.
func CreateBranch(root, slug string, cfg *Config) (*BranchManifest, error) {
	f, err := LoadFeature(root, slug)
	if err != nil {
		return nil, err
	}

	// Phase gate: branches can only be created at PhasePlanned or later
	plannedIdx := PhaseIndex(PhasePlanned)
	currentIdx := PhaseIndex(f.Phase)
	if currentIdx < plannedIdx {
		return nil, fmt.Errorf("%w: feature must be in planned phase or later (current: %s)", ErrInvalidTransition, f.Phase)
	}

	branchName := cfg.Branches.FeaturePrefix + slug

	// Check if branch manifest already exists
	path := BranchPath(root, branchName)
	if _, err := os.Stat(path); err == nil {
		return nil, ErrBranchExists
	}

	manifest := &BranchManifest{
		Name:       branchName,
		Feature:    slug,
		BaseBranch: cfg.Branches.Main,
		CreatedAt:  time.Now().UTC(),
	}

	if err := SaveBranch(root, manifest); err != nil {
		return nil, err
	}

	// Update feature with branch reference
	f.Branch = branchName
	if err := f.Save(root); err != nil {
		return nil, err
	}

	return manifest, nil
}

// LoadBranch reads a branch manifest from disk.
func LoadBranch(root, branchName string) (*BranchManifest, error) {
	path := BranchPath(root, branchName)
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, ErrBranchNotFound
		}
		return nil, fmt.Errorf("read branch manifest: %w", err)
	}

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

// SaveBranch writes a branch manifest to disk.
func SaveBranch(root string, manifest *BranchManifest) error {
	path := BranchPath(root, manifest.Name)
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fmt.Errorf("create branch directory: %w", err)
	}

	data, err := yaml.Marshal(manifest)
	if err != nil {
		return fmt.Errorf("marshal branch manifest: %w", err)
	}

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

// MergeChecklist returns a list of unmet gates that block merging.
// An empty list means the feature is ready to merge.
func MergeChecklist(root, slug string) ([]string, error) {
	f, err := LoadFeature(root, slug)
	if err != nil {
		return nil, err
	}

	var unmet []string

	// Must be in merge phase
	if f.Phase != PhaseMerge {
		unmet = append(unmet, fmt.Sprintf("feature is in phase %s, must be in merge", f.Phase))
	}

	// Review must be passed
	if art := f.GetArtifact(ArtifactReview); art == nil || art.Status != StatusPassed {
		unmet = append(unmet, "code review not passed")
	}

	// Audit must be passed
	if art := f.GetArtifact(ArtifactAudit); art == nil || art.Status != StatusPassed {
		unmet = append(unmet, "security audit not passed")
	}

	// QA must be passed
	if art := f.GetArtifact(ArtifactQAResults); art == nil || art.Status != StatusPassed {
		unmet = append(unmet, "QA tests not passed")
	}

	// No blockers
	if f.IsBlocked() {
		unmet = append(unmet, fmt.Sprintf("feature has %d blocker(s)", len(f.Blockers)))
	}

	return unmet, nil
}
