package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/spf13/cobra"
	"github.com/steveyegge/beads/internal/beads"
	"github.com/steveyegge/beads/internal/types"
	"github.com/steveyegge/beads/internal/ui"
)

// legacyDeletionRecordCmd represents a single deletion entry from the legacy deletions.jsonl manifest.
// This is inlined here for migration purposes only - new code uses inline tombstones.
type legacyDeletionRecordCmd struct {
	ID        string    `json:"id"`               // Issue ID that was deleted
	Timestamp time.Time `json:"ts"`               // When the deletion occurred
	Actor     string    `json:"by"`               // Who performed the deletion
	Reason    string    `json:"reason,omitempty"` // Optional reason for deletion
}

// loadLegacyDeletionsCmd reads the legacy deletions.jsonl manifest.
// Returns a map of deletion records keyed by issue ID and any warnings.
// This is inlined here for migration purposes only.
func loadLegacyDeletionsCmd(path string) (map[string]legacyDeletionRecordCmd, []string, error) {
	records := make(map[string]legacyDeletionRecordCmd)
	var warnings []string

	f, err := os.Open(path) // #nosec G304 - controlled path from caller
	if err != nil {
		if os.IsNotExist(err) {
			return records, nil, nil
		}
		return nil, nil, fmt.Errorf("failed to open deletions file: %w", err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	scanner.Buffer(make([]byte, 0, 1024), 1024*1024)

	lineNum := 0
	for scanner.Scan() {
		lineNum++
		line := scanner.Text()
		if line == "" {
			continue
		}

		var record legacyDeletionRecordCmd
		if err := json.Unmarshal([]byte(line), &record); err != nil {
			warnings = append(warnings, fmt.Sprintf("line %d: invalid JSON", lineNum))
			continue
		}
		if record.ID == "" {
			warnings = append(warnings, fmt.Sprintf("line %d: missing ID", lineNum))
			continue
		}
		records[record.ID] = record
	}

	if err := scanner.Err(); err != nil {
		return nil, nil, fmt.Errorf("error reading deletions file: %w", err)
	}

	return records, warnings, nil
}

// TODO: Consider integrating into 'bd doctor' migration detection
var migrateTombstonesCmd = &cobra.Command{
	Use:     "tombstones",
	Short:   "Convert deletions.jsonl entries to inline tombstones",
	Long: `Migrate legacy deletions.jsonl entries to inline tombstones in issues.jsonl.

This command converts existing deletion records from the legacy deletions.jsonl
manifest to inline tombstone entries in issues.jsonl. This is part of the
transition from separate deletion tracking to unified tombstone-based deletion.

The migration:
1. Reads existing deletions from deletions.jsonl
2. Checks issues.jsonl for already-existing tombstones
3. Creates tombstone entries for unmigrated deletions
4. Appends new tombstones to issues.jsonl
5. Archives deletions.jsonl with .migrated suffix

Use --dry-run to preview changes without modifying files.

Examples:
  bd migrate-tombstones           # Migrate deletions to tombstones
  bd migrate-tombstones --dry-run # Preview what would be migrated
  bd migrate-tombstones --verbose # Show detailed progress`,
	Run: func(cmd *cobra.Command, _ []string) {
		dryRun, _ := cmd.Flags().GetBool("dry-run")
		verbose, _ := cmd.Flags().GetBool("verbose")

		// Block writes in readonly mode
		if !dryRun {
			CheckReadonly("migrate-tombstones")
		}

		// Find .beads directory
		beadsDir := beads.FindBeadsDir()
		if beadsDir == "" {
			if jsonOutput {
				outputJSON(map[string]interface{}{
					"error":   "no_beads_directory",
					"message": "No .beads directory found. Run 'bd init' first.",
				})
			} else {
				fmt.Fprintf(os.Stderr, "Error: no .beads directory found\n")
				fmt.Fprintf(os.Stderr, "Hint: run 'bd init' to initialize bd\n")
			}
			os.Exit(1)
		}

		// Check paths
		deletionsPath := filepath.Join(beadsDir, "deletions.jsonl")
		issuesPath := filepath.Join(beadsDir, "issues.jsonl")

		// Load existing deletions
		records, warnings, err := loadLegacyDeletionsCmd(deletionsPath)
		if err != nil {
			if jsonOutput {
				outputJSON(map[string]interface{}{
					"error":   "load_deletions_failed",
					"message": err.Error(),
				})
			} else {
				fmt.Fprintf(os.Stderr, "Error loading deletions.jsonl: %v\n", err)
			}
			os.Exit(1)
		}

		if len(records) == 0 {
			if jsonOutput {
				outputJSON(map[string]interface{}{
					"status":   "noop",
					"message":  "No deletions to migrate",
					"migrated": 0,
					"skipped":  0,
				})
			} else {
				fmt.Println("No deletions.jsonl entries to migrate")
			}
			return
		}

		// Print warnings from loading
		for _, warning := range warnings {
			if !jsonOutput {
				fmt.Printf("%s\n", ui.RenderWarn(fmt.Sprintf("Warning: %s", warning)))
			}
		}

		// Load existing issues.jsonl to find existing tombstones
		existingTombstones := make(map[string]bool)
		if _, err := os.Stat(issuesPath); err == nil {
			// nolint:gosec // G304: issuesPath is controlled from beadsDir
			file, err := os.Open(issuesPath)
			if err != nil {
				if jsonOutput {
					outputJSON(map[string]interface{}{
						"error":   "load_issues_failed",
						"message": err.Error(),
					})
				} else {
					fmt.Fprintf(os.Stderr, "Error opening issues.jsonl: %v\n", err)
				}
				os.Exit(1)
			}

			decoder := json.NewDecoder(file)
			for {
				var issue types.Issue
				if err := decoder.Decode(&issue); err != nil {
					if err.Error() == "EOF" {
						break
					}
					// Skip corrupt lines, continue reading
					continue
				}
				if issue.IsTombstone() {
					existingTombstones[issue.ID] = true
				}
			}
			_ = file.Close()
		}

		// Determine which deletions need migration
		var toMigrate []legacyDeletionRecordCmd
		var skippedIDs []string
		for id, record := range records {
			if existingTombstones[id] {
				skippedIDs = append(skippedIDs, id)
				if verbose && !jsonOutput {
					fmt.Printf("  Skipping %s (tombstone already exists)\n", id)
				}
			} else {
				toMigrate = append(toMigrate, record)
			}
		}

		if len(toMigrate) == 0 {
			if jsonOutput {
				outputJSON(map[string]interface{}{
					"status":   "noop",
					"message":  "All deletions already migrated to tombstones",
					"migrated": 0,
					"skipped":  len(skippedIDs),
				})
			} else {
				fmt.Printf("All %d deletion(s) already have tombstones in issues.jsonl\n", len(skippedIDs))
			}
			return
		}

		// Dry run - just report what would happen
		if dryRun {
			if jsonOutput {
				outputJSON(map[string]interface{}{
					"dry_run":       true,
					"would_migrate": len(toMigrate),
					"skipped":       len(skippedIDs),
					"total":         len(records),
				})
			} else {
				fmt.Println("Dry run mode - no changes will be made")
				fmt.Printf("\nWould migrate %d deletion(s) to tombstones:\n", len(toMigrate))
				for _, record := range toMigrate {
					fmt.Printf("  - %s (deleted %s by %s)\n",
						record.ID,
						record.Timestamp.Format("2006-01-02"),
						record.Actor)
				}
				if len(skippedIDs) > 0 {
					fmt.Printf("\nWould skip %d already-migrated deletion(s)\n", len(skippedIDs))
				}
			}
			return
		}

		// Perform migration - append tombstones to issues.jsonl
		if verbose && !jsonOutput {
			fmt.Printf("Creating %d tombstone(s)...\n", len(toMigrate))
		}

		// Open issues.jsonl for appending
		// nolint:gosec // G304: issuesPath is controlled from beadsDir
		file, err := os.OpenFile(issuesPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
		if err != nil {
			if jsonOutput {
				outputJSON(map[string]interface{}{
					"error":   "open_issues_failed",
					"message": err.Error(),
				})
			} else {
				fmt.Fprintf(os.Stderr, "Error opening issues.jsonl for append: %v\n", err)
			}
			os.Exit(1)
		}
		defer file.Close()

		encoder := json.NewEncoder(file)
		var migratedIDs []string
		for _, record := range toMigrate {
			tombstone := convertLegacyDeletionRecordToTombstone(record)
			if err := encoder.Encode(tombstone); err != nil {
				if jsonOutput {
					outputJSON(map[string]interface{}{
						"error":    "write_tombstone_failed",
						"message":  err.Error(),
						"issue_id": record.ID,
					})
				} else {
					fmt.Fprintf(os.Stderr, "Error writing tombstone for %s: %v\n", record.ID, err)
				}
				os.Exit(1)
			}
			migratedIDs = append(migratedIDs, record.ID)
			if verbose && !jsonOutput {
				fmt.Printf("  ✓ Created tombstone for %s\n", record.ID)
			}
		}

		// Archive deletions.jsonl
		archivePath := deletionsPath + ".migrated"
		if err := os.Rename(deletionsPath, archivePath); err != nil {
			// Warn but don't fail - tombstones were already created
			if !jsonOutput {
				fmt.Printf("%s\n", ui.RenderWarn(fmt.Sprintf("Warning: could not archive deletions.jsonl: %v", err)))
			}
		} else if verbose && !jsonOutput {
			fmt.Printf("  ✓ Archived deletions.jsonl to %s\n", filepath.Base(archivePath))
		}

		// Success output
		if jsonOutput {
			outputJSON(map[string]interface{}{
				"status":       "success",
				"migrated":     len(migratedIDs),
				"skipped":      len(skippedIDs),
				"total":        len(records),
				"archive":      archivePath,
				"migrated_ids": migratedIDs,
			})
		} else {
			fmt.Printf("\n%s\n\n", ui.RenderPass("✓ Migration complete"))
			fmt.Printf("  Migrated: %d tombstone(s)\n", len(migratedIDs))
			if len(skippedIDs) > 0 {
				fmt.Printf("  Skipped:  %d (already had tombstones)\n", len(skippedIDs))
			}
			fmt.Printf("  Archived: %s\n", filepath.Base(archivePath))
			fmt.Println("\nNext steps:")
			fmt.Println("  1. Run 'bd sync' to propagate tombstones to remote")
			fmt.Println("  2. Other clones will receive tombstones on next sync")
		}
	},
}

// convertLegacyDeletionRecordToTombstone creates a tombstone issue from a legacy deletion record.
func convertLegacyDeletionRecordToTombstone(del legacyDeletionRecordCmd) *types.Issue {
	deletedAt := del.Timestamp
	return &types.Issue{
		ID:           del.ID,
		Title:        "(deleted)",
		Description:  "",
		Status:       types.StatusTombstone,
		Priority:     0,              // Unknown priority (0 = unset)
		IssueType:    types.TypeTask, // Default type (must be valid)
		CreatedAt:    del.Timestamp,
		UpdatedAt:    del.Timestamp,
		DeletedAt:    &deletedAt,
		DeletedBy:    del.Actor,
		DeleteReason: del.Reason,
		OriginalType: "", // Not available in legacy deletions.jsonl
	}
}

func init() {
	migrateTombstonesCmd.Flags().Bool("dry-run", false, "Preview changes without modifying files")
	migrateTombstonesCmd.Flags().Bool("verbose", false, "Show detailed progress")
	migrateTombstonesCmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
	migrateCmd.AddCommand(migrateTombstonesCmd)

	// Backwards compatibility alias at root level (hidden)
	migrateTombstonesAliasCmd := *migrateTombstonesCmd
	migrateTombstonesAliasCmd.Use = "migrate-tombstones"
	migrateTombstonesAliasCmd.Hidden = true
	migrateTombstonesAliasCmd.Deprecated = "use 'bd migrate tombstones' instead"
	rootCmd.AddCommand(&migrateTombstonesAliasCmd)
}
