package main

import (
	"context"
	"fmt"
	"time"

	"github.com/steveyegge/beads/internal/storage"
	"github.com/steveyegge/beads/internal/storage/sqlite"
	"github.com/steveyegge/beads/internal/types"
)

// ImportOptions configures how the import behaves
type ImportOptions struct {
	ResolveCollisions bool // Auto-resolve collisions by remapping to new IDs
	DryRun            bool // Preview changes without applying them
	SkipUpdate        bool // Skip updating existing issues (create-only mode)
	Strict            bool // Fail on any error (dependencies, labels, etc.)
}

// ImportResult contains statistics about the import operation
type ImportResult struct {
	Created      int               // New issues created
	Updated      int               // Existing issues updated
	Skipped      int               // Issues skipped (duplicates, errors)
	Collisions   int               // Collisions detected
	IDMapping    map[string]string // Mapping of remapped IDs (old -> new)
	CollisionIDs []string          // IDs that collided
}

// importIssuesCore handles the core import logic used by both manual and auto-import.
// This function:
// - Opens a direct SQLite connection if needed (daemon mode)
// - Detects and handles collisions
// - Imports issues, dependencies, and labels
// - Returns detailed results
//
// The caller is responsible for:
// - Reading and parsing JSONL into issues slice
// - Displaying results to the user
// - Setting metadata (e.g., last_import_hash)
func importIssuesCore(ctx context.Context, dbPath string, store storage.Storage, issues []*types.Issue, opts ImportOptions) (*ImportResult, error) {
	result := &ImportResult{
		IDMapping: make(map[string]string),
	}

	// Phase 1: Get or create SQLite store
	// Import needs direct SQLite access for collision detection
	var sqliteStore *sqlite.SQLiteStorage
	var needCloseStore bool

	if store != nil {
		// Direct mode - try to use existing store
		var ok bool
		sqliteStore, ok = store.(*sqlite.SQLiteStorage)
		if !ok {
			return nil, fmt.Errorf("collision detection requires SQLite storage backend")
		}
	} else {
		// Daemon mode - open direct connection for import
		if dbPath == "" {
			return nil, fmt.Errorf("database path not set")
		}
		var err error
		sqliteStore, err = sqlite.New(dbPath)
		if err != nil {
			return nil, fmt.Errorf("failed to open database: %w", err)
		}
		needCloseStore = true
		defer func() {
			if needCloseStore {
				sqliteStore.Close()
			}
		}()
	}

	// Phase 2: Detect collisions
	collisionResult, err := sqlite.DetectCollisions(ctx, sqliteStore, issues)
	if err != nil {
		return nil, fmt.Errorf("collision detection failed: %w", err)
	}

	result.Collisions = len(collisionResult.Collisions)
	for _, collision := range collisionResult.Collisions {
		result.CollisionIDs = append(result.CollisionIDs, collision.ID)
	}

	// Phase 3: Handle collisions
	if len(collisionResult.Collisions) > 0 {
		if opts.DryRun {
			// In dry-run mode, just return collision info
			return result, nil
		}

		if !opts.ResolveCollisions {
			// Default behavior: fail on collision
			return result, fmt.Errorf("collision detected for issues: %v (use ResolveCollisions to auto-resolve)", result.CollisionIDs)
		}

		// Resolve collisions by scoring and remapping
		allExistingIssues, err := sqliteStore.SearchIssues(ctx, "", types.IssueFilter{})
		if err != nil {
			return nil, fmt.Errorf("failed to get existing issues for collision resolution: %w", err)
		}

		// Score collisions
		if err := sqlite.ScoreCollisions(ctx, sqliteStore, collisionResult.Collisions, allExistingIssues); err != nil {
			return nil, fmt.Errorf("failed to score collisions: %w", err)
		}

		// Remap collisions
		idMapping, err := sqlite.RemapCollisions(ctx, sqliteStore, collisionResult.Collisions, allExistingIssues)
		if err != nil {
			return nil, fmt.Errorf("failed to remap collisions: %w", err)
		}

		result.IDMapping = idMapping
		result.Created = len(collisionResult.Collisions)

		// Remove colliding issues from the list (they're already processed)
		filteredIssues := make([]*types.Issue, 0)
		collidingIDs := make(map[string]bool)
		for _, collision := range collisionResult.Collisions {
			collidingIDs[collision.ID] = true
		}
		for _, issue := range issues {
			if !collidingIDs[issue.ID] {
				filteredIssues = append(filteredIssues, issue)
			}
		}
		issues = filteredIssues
	} else if opts.DryRun {
		// No collisions in dry-run mode
		result.Created = len(collisionResult.NewIssues)
		result.Updated = len(collisionResult.ExactMatches)
		return result, nil
	}

	// Phase 4: Import remaining issues (exact matches and new issues)
	var newIssues []*types.Issue
	seenNew := make(map[string]int) // Track duplicates within import batch

	for _, issue := range issues {
		// Check if issue exists in DB
		existing, err := sqliteStore.GetIssue(ctx, issue.ID)
		if err != nil {
			return nil, fmt.Errorf("error checking issue %s: %w", issue.ID, err)
		}

		if existing != nil {
			// Issue exists - update it unless SkipUpdate is set
			if opts.SkipUpdate {
				result.Skipped++
				continue
			}

			// Build updates map
			updates := make(map[string]interface{})
			updates["title"] = issue.Title
			updates["description"] = issue.Description
			updates["status"] = issue.Status
			updates["priority"] = issue.Priority
			updates["issue_type"] = issue.IssueType
			updates["design"] = issue.Design
			updates["acceptance_criteria"] = issue.AcceptanceCriteria
			updates["notes"] = issue.Notes

			if issue.Assignee != "" {
				updates["assignee"] = issue.Assignee
			} else {
				updates["assignee"] = nil
			}

			if issue.ExternalRef != nil && *issue.ExternalRef != "" {
				updates["external_ref"] = *issue.ExternalRef
			} else {
				updates["external_ref"] = nil
			}

			if err := sqliteStore.UpdateIssue(ctx, issue.ID, updates, "import"); err != nil {
				return nil, fmt.Errorf("error updating issue %s: %w", issue.ID, err)
			}
			result.Updated++
		} else {
			// New issue - check for duplicates in import batch
			if idx, seen := seenNew[issue.ID]; seen {
				if opts.Strict {
					return nil, fmt.Errorf("duplicate issue ID %s in import (line %d)", issue.ID, idx)
				}
				result.Skipped++
				continue
			}
			seenNew[issue.ID] = len(newIssues)
			newIssues = append(newIssues, issue)
		}
	}

	// Batch create all new issues
	if len(newIssues) > 0 {
		if err := sqliteStore.CreateIssues(ctx, newIssues, "import"); err != nil {
			return nil, fmt.Errorf("error creating issues: %w", err)
		}
		result.Created += len(newIssues)
	}

	// Sync counters after batch import
	if err := sqliteStore.SyncAllCounters(ctx); err != nil {
		return nil, fmt.Errorf("error syncing counters: %w", err)
	}

	// Phase 5: Import dependencies
	for _, issue := range issues {
		if len(issue.Dependencies) == 0 {
			continue
		}

		for _, dep := range issue.Dependencies {
			// Check if dependency already exists
			existingDeps, err := sqliteStore.GetDependencyRecords(ctx, dep.IssueID)
			if err != nil {
				return nil, fmt.Errorf("error checking dependencies for %s: %w", dep.IssueID, err)
			}

			// Check for duplicate
			isDuplicate := false
			for _, existing := range existingDeps {
				if existing.DependsOnID == dep.DependsOnID && existing.Type == dep.Type {
					isDuplicate = true
					break
				}
			}

			if isDuplicate {
				continue
			}

			// Add dependency
			if err := sqliteStore.AddDependency(ctx, dep, "import"); err != nil {
				if opts.Strict {
					return nil, fmt.Errorf("error adding dependency %s → %s: %w", dep.IssueID, dep.DependsOnID, err)
				}
				// Non-strict mode: just skip this dependency
				continue
			}
		}
	}

	// Phase 6: Import labels
	for _, issue := range issues {
		if len(issue.Labels) == 0 {
			continue
		}

		// Get current labels
		currentLabels, err := sqliteStore.GetLabels(ctx, issue.ID)
		if err != nil {
			return nil, fmt.Errorf("error getting labels for %s: %w", issue.ID, err)
		}

		currentLabelSet := make(map[string]bool)
		for _, label := range currentLabels {
			currentLabelSet[label] = true
		}

		// Add missing labels
		for _, label := range issue.Labels {
			if !currentLabelSet[label] {
				if err := sqliteStore.AddLabel(ctx, issue.ID, label, "import"); err != nil {
					if opts.Strict {
						return nil, fmt.Errorf("error adding label %s to %s: %w", label, issue.ID, err)
					}
					// Non-strict mode: skip this label
					continue
				}
			}
		}
	}

	// Phase 7: Import comments
	for _, issue := range issues {
		if len(issue.Comments) == 0 {
			continue
		}

		// Get current comments to avoid duplicates
		currentComments, err := sqliteStore.GetIssueComments(ctx, issue.ID)
		if err != nil {
			return nil, fmt.Errorf("error getting comments for %s: %w", issue.ID, err)
		}

		// Build a set of existing comments (by author+text+timestamp)
		existingComments := make(map[string]bool)
		for _, c := range currentComments {
			key := fmt.Sprintf("%s:%s:%s", c.Author, c.Text, c.CreatedAt.Format(time.RFC3339))
			existingComments[key] = true
		}

		// Add missing comments
		for _, comment := range issue.Comments {
			key := fmt.Sprintf("%s:%s:%s", comment.Author, comment.Text, comment.CreatedAt.Format(time.RFC3339))
			if !existingComments[key] {
				if _, err := sqliteStore.AddIssueComment(ctx, issue.ID, comment.Author, comment.Text); err != nil {
					if opts.Strict {
						return nil, fmt.Errorf("error adding comment to %s: %w", issue.ID, err)
					}
					// Non-strict mode: skip this comment
					continue
				}
			}
		}
	}

	return result, nil
}
