package commands

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

	"github.com/tanagram/cli/checker"
	"github.com/tanagram/cli/extractor"
	"github.com/tanagram/cli/git"
	"github.com/tanagram/cli/parser"
	"github.com/tanagram/cli/storage"
)

// spinner shows a simple spinner animation
func spinner(stop chan bool, message string) {
	chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
	i := 0
	for {
		select {
		case <-stop:
			fmt.Print("\r")
			return
		default:
			fmt.Printf("\r%s %s", chars[i%len(chars)], message)
			i++
			time.Sleep(100 * time.Millisecond)
		}
	}
}

// Run executes the main policy check with auto-sync
func Run() error {
	// Find git root
	gitRoot, err := storage.FindGitRoot()
	if err != nil {
		return err
	}

	// Find all instruction files
	instructionFiles, err := FindInstructionFiles(gitRoot)
	if err != nil {
		return err
	}

	if len(instructionFiles) == 0 {
		return fmt.Errorf("no instruction files found (looking for AGENTS.md, POLICIES.md, CLAUDE.md, BUGBOT.md, .cursor/rules/*.mdc)")
	}

	// Load cache
	cache, err := storage.LoadCache(gitRoot)
	if err != nil {
		return fmt.Errorf("failed to load cache: %w", err)
	}

	// Check each file for changes and collect which files need syncing
	var filesToSync []string
	for _, file := range instructionFiles {
		changed, err := cache.HasChanged(file)
		if err != nil {
			return fmt.Errorf("failed to check if %s changed: %w", file, err)
		}
		if changed {
			filesToSync = append(filesToSync, file)
		}
	}

	// Auto-sync only the files that changed
	if len(filesToSync) > 0 {
		fmt.Printf("\nSyncing policies with LLM (processing %d changed file(s) in parallel)...\n", len(filesToSync))

		ctx := context.Background()

		// Result type for collecting sync results
		type syncResult struct {
			file     string
			relPath  string
			policies []parser.Policy
			err      error
		}

		// Channel to collect results
		results := make(chan syncResult, len(filesToSync))
		var wg sync.WaitGroup

		// Start spinner
		stop := make(chan bool)
		var completed int
		var mu sync.Mutex
		go func() {
			chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
			i := 0
			for {
				select {
				case <-stop:
					fmt.Print("\r")
					return
				default:
					mu.Lock()
					c := completed
					mu.Unlock()
					fmt.Printf("\r%s Processing files... (%d/%d completed)", chars[i%len(chars)], c, len(filesToSync))
					i++
					time.Sleep(100 * time.Millisecond)
				}
			}
		}()

		// Launch goroutines only for changed files
		for _, file := range filesToSync {
			wg.Add(1)
			go func(file string) {
				defer wg.Done()
				relPath, _ := filepath.Rel(gitRoot, file)
				policies, err := extractor.ExtractPoliciesFromFile(ctx, file)
				results <- syncResult{file, relPath, policies, err}
			}(file)
		}

		// Close results channel when all goroutines complete
		go func() {
			wg.Wait()
			close(results)
		}()

		// Collect results
		totalPolicies := 0
		for result := range results {
			if result.err != nil {
				stop <- true
				close(stop)
				time.Sleep(50 * time.Millisecond)
				mu.Lock()
				fmt.Printf("\r\033[K✗ Failed to process %s\n", result.relPath)
				mu.Unlock()
				return fmt.Errorf("failed to extract policies from %s: %w", result.file, result.err)
			}

			if err := cache.UpdateFile(result.file, result.policies); err != nil {
				stop <- true
				close(stop)
				time.Sleep(50 * time.Millisecond)
				return fmt.Errorf("failed to update cache for %s: %w", result.file, err)
			}

			totalPolicies += len(result.policies)

			// Atomic update of counter and output (prevents race with spinner)
			mu.Lock()
			completed++
			fmt.Printf("\r\033[K✓ %s - %d policies\n", result.relPath, len(result.policies))
			mu.Unlock()
		}

		// Stop spinner
		stop <- true
		close(stop)
		time.Sleep(50 * time.Millisecond)

		if err := cache.Save(); err != nil {
			return fmt.Errorf("failed to save cache: %w", err)
		}

		fmt.Printf("\n✓ Synced %d policies from %d changed file(s)\n", totalPolicies, len(filesToSync))
	}

	// Load all policies from cache
	policies, err := cache.GetAllPolicies()
	if err != nil {
		return fmt.Errorf("failed to load policies from cache: %w", err)
	}

	if len(policies) == 0 {
		fmt.Println("No enforceable policies found")
		return nil
	}

	fmt.Printf("Loaded %d policies\n", len(policies))

	// Get all changes (unstaged + staged)
	fmt.Println("Checking all changes (unstaged + staged)...")
	diffResult, err := git.GetAllChanges()
	if err != nil {
		return fmt.Errorf("error getting git diff: %w", err)
	}

	if len(diffResult.Changes) == 0 {
		fmt.Println("No changes to check")
		return nil
	}

	fmt.Printf("Scanning %d changed lines...\n\n", len(diffResult.Changes))

	// Check changes against policies (both regex and LLM-based)
	ctx := context.Background()
	result := checker.CheckChanges(ctx, diffResult.Changes, policies)

	// Handle results based on whether violations were found
	if len(result.Violations) > 0 {
		// Format and output violations to stderr
		violationOutput := checker.FormatViolations(result)
		fmt.Fprint(os.Stderr, violationOutput)

		// Format and output Claude-friendly instructions to stderr
		claudeInstructions := checker.FormatClaudeInstructions(result)
		fmt.Fprint(os.Stderr, claudeInstructions)

		// Exit with code 2 to trigger Claude Code hook behavior
		os.Exit(2)
	}

	// No violations found - output success message
	fmt.Println("✓ No policy violations found")
	return nil
}
