package tui

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"

	"dbbackup/internal/config"
	"dbbackup/internal/logger"
	"dbbackup/internal/restore"
)

var (
	archiveHeaderStyle = lipgloss.NewStyle().
				Bold(true).
				Foreground(lipgloss.Color("240"))

	archiveSelectedStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("15")).
				Bold(true)

	archiveNormalStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("250"))

	archiveInvalidStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("1"))

	archiveOldStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("3"))
)

// ArchiveInfo holds information about a backup archive
type ArchiveInfo struct {
	Name          string
	Path          string
	Format        restore.ArchiveFormat
	Size          int64
	Modified      time.Time
	DatabaseName  string
	Valid         bool
	ValidationMsg string
	ExtractedDir  string // Pre-extracted cluster directory (optimization)
}

// ArchiveBrowserModel for browsing and selecting backup archives
type ArchiveBrowserModel struct {
	config     *config.Config
	logger     logger.Logger
	parent     tea.Model
	ctx        context.Context
	archives   []ArchiveInfo
	cursor     int
	loading    bool
	err        error
	mode       string // "restore-single", "restore-cluster", "manage"
	filterType string // "all", "postgres", "mysql", "cluster"
	message    string
}

// NewArchiveBrowser creates a new archive browser
func NewArchiveBrowser(cfg *config.Config, log logger.Logger, parent tea.Model, ctx context.Context, mode string) ArchiveBrowserModel {
	return ArchiveBrowserModel{
		config:     cfg,
		logger:     log,
		parent:     parent,
		ctx:        ctx,
		loading:    true,
		mode:       mode,
		filterType: "all",
	}
}

func (m ArchiveBrowserModel) Init() tea.Cmd {
	return loadArchives(m.config, m.logger)
}

type archiveListMsg struct {
	archives []ArchiveInfo
	err      error
}

func loadArchives(cfg *config.Config, log logger.Logger) tea.Cmd {
	return func() tea.Msg {
		backupDir := cfg.BackupDir

		// Check if backup directory exists
		if _, err := os.Stat(backupDir); err != nil {
			return archiveListMsg{archives: nil, err: fmt.Errorf("backup directory not found: %s", backupDir)}
		}

		// List all files
		files, err := os.ReadDir(backupDir)
		if err != nil {
			return archiveListMsg{archives: nil, err: fmt.Errorf("cannot read backup directory: %w", err)}
		}

		var archives []ArchiveInfo

		for _, file := range files {
			if file.IsDir() {
				continue
			}

			name := file.Name()
			format := restore.DetectArchiveFormat(name)

			if format == restore.FormatUnknown {
				continue // Skip non-backup files
			}

			info, _ := file.Info()
			fullPath := filepath.Join(backupDir, name)

			// Extract database name
			dbName := extractDBNameFromFilename(name)

			// Basic validation (just check if file is readable)
			valid := true
			validationMsg := "Valid"
			if info.Size() == 0 {
				valid = false
				validationMsg = "Empty file"
			}

			archives = append(archives, ArchiveInfo{
				Name:          name,
				Path:          fullPath,
				Format:        format,
				Size:          info.Size(),
				Modified:      info.ModTime(),
				DatabaseName:  dbName,
				Valid:         valid,
				ValidationMsg: validationMsg,
			})
		}

		// Sort by modification time (newest first)
		sort.Slice(archives, func(i, j int) bool {
			return archives[i].Modified.After(archives[j].Modified)
		})

		return archiveListMsg{archives: archives, err: nil}
	}
}

func (m ArchiveBrowserModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case archiveListMsg:
		m.loading = false
		if msg.err != nil {
			m.err = msg.err
			return m, nil
		}
		m.archives = m.filterArchives(msg.archives)
		if len(m.archives) == 0 {
			m.message = "No backup archives found"
		}
		// Auto-forward in auto-confirm mode
		if m.config.TUIAutoConfirm {
			return m.parent, tea.Quit
		}
		return m, nil

	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "q", "esc":
			return m.parent, nil

		case "up", "k":
			if m.cursor > 0 {
				m.cursor--
			}

		case "down", "j":
			if m.cursor < len(m.archives)-1 {
				m.cursor++
			}

		case "f":
			// Toggle filter
			filters := []string{"all", "postgres", "mysql", "cluster"}
			for i, f := range filters {
				if f == m.filterType {
					m.filterType = filters[(i+1)%len(filters)]
					break
				}
			}
			m.cursor = 0
			return m, loadArchives(m.config, m.logger)

		case "enter", " ":
			if len(m.archives) > 0 && m.cursor < len(m.archives) {
				selected := m.archives[m.cursor]

				// Handle diagnose mode - go directly to diagnosis view
				if m.mode == "diagnose" {
					diagnoseView := NewDiagnoseView(m.config, m.logger, m.parent, m.ctx, selected)
					return diagnoseView, diagnoseView.Init()
				}

				// Validate selection based on mode
				if m.mode == "restore-cluster" && !selected.Format.IsClusterBackup() {
					m.message = errorStyle.Render("[FAIL] Please select a cluster backup (.tar.gz)")
					return m, nil
				}

				if m.mode == "restore-single" && selected.Format.IsClusterBackup() {
					// Cluster backup selected in single restore mode - offer to select individual database
					clusterSelector := NewClusterDatabaseSelector(m.config, m.logger, m, m.ctx, selected, "single", false)
					return clusterSelector, clusterSelector.Init()
				}

				// Open restore preview
				preview := NewRestorePreview(m.config, m.logger, m.parent, m.ctx, selected, m.mode)
				return preview, preview.Init()
			}

		case "s":
			// Select single database from cluster (shortcut key)
			if len(m.archives) > 0 && m.cursor < len(m.archives) {
				selected := m.archives[m.cursor]
				if selected.Format.IsClusterBackup() {
					clusterSelector := NewClusterDatabaseSelector(m.config, m.logger, m, m.ctx, selected, "single", false)
					return clusterSelector, clusterSelector.Init()
				} else {
					m.message = infoStyle.Render("💡 [s] only works with cluster backups")
				}
			}

		case "i":
			// Show detailed info
			if len(m.archives) > 0 && m.cursor < len(m.archives) {
				selected := m.archives[m.cursor]
				m.message = fmt.Sprintf("[PKG] %s | Format: %s | Size: %s | Modified: %s",
					selected.Name,
					selected.Format.String(),
					formatSize(selected.Size),
					selected.Modified.Format("2006-01-02 15:04:05"))
			}

		case "d":
			// Run diagnosis on selected archive
			if len(m.archives) > 0 && m.cursor < len(m.archives) {
				selected := m.archives[m.cursor]
				diagnoseView := NewDiagnoseView(m.config, m.logger, m, m.ctx, selected)
				return diagnoseView, diagnoseView.Init()
			}

		case "p":
			// Show system profile before restore
			profile := NewProfileModel(m.config, m.logger, m)
			return profile, profile.Init()
		}
	}

	return m, nil
}

func (m ArchiveBrowserModel) View() string {
	var s strings.Builder

	// Header
	title := "[SELECT] Backup Archives"
	if m.mode == "restore-single" {
		title = "[SELECT] Select Archive to Restore (Single Database)"
	} else if m.mode == "restore-cluster" {
		title = "[SELECT] Select Archive to Restore (Cluster)"
	} else if m.mode == "diagnose" {
		title = "[SELECT] Select Archive to Diagnose"
	}

	s.WriteString(titleStyle.Render(title))
	s.WriteString("\n\n")

	if m.loading {
		s.WriteString(infoStyle.Render("Loading archives..."))
		return s.String()
	}

	if m.err != nil {
		s.WriteString(errorStyle.Render(fmt.Sprintf("[FAIL] Error: %v", m.err)))
		s.WriteString("\n\n")
		s.WriteString(infoStyle.Render("Press Esc to go back"))
		return s.String()
	}

	// Filter info
	filterLabel := "Filter: " + m.filterType
	s.WriteString(infoStyle.Render(filterLabel))
	s.WriteString(infoStyle.Render("  (Press 'f' to change filter)"))
	s.WriteString("\n\n")

	// Archives list
	if len(m.archives) == 0 {
		s.WriteString(infoStyle.Render(m.message))
		s.WriteString("\n\n")
		s.WriteString(infoStyle.Render("Press Esc to go back"))
		return s.String()
	}

	// Column headers
	s.WriteString(archiveHeaderStyle.Render(fmt.Sprintf("%-40s %-25s %-12s %-20s",
		"FILENAME", "FORMAT", "SIZE", "MODIFIED")))
	s.WriteString("\n")
	s.WriteString(strings.Repeat("-", 100))
	s.WriteString("\n")

	// Show archives (limit to visible area)
	start := m.cursor - 5
	if start < 0 {
		start = 0
	}
	end := start + 10
	if end > len(m.archives) {
		end = len(m.archives)
	}

	for i := start; i < end; i++ {
		archive := m.archives[i]
		cursor := " "
		style := archiveNormalStyle

		if i == m.cursor {
			cursor = ">"
			style = archiveSelectedStyle
		}

		// Color code based on validity and age
		statusIcon := "[+]"
		if !archive.Valid {
			statusIcon = "[-]"
			style = archiveInvalidStyle
		} else if time.Since(archive.Modified) > 30*24*time.Hour {
			style = archiveOldStyle
			statusIcon = "[WARN]"
		}

		filename := truncate(archive.Name, 38)
		format := truncate(archive.Format.String(), 23)

		line := fmt.Sprintf("%s %s %-38s %-23s %-10s %-19s",
			cursor,
			statusIcon,
			filename,
			format,
			formatSize(archive.Size),
			archive.Modified.Format("2006-01-02 15:04"))

		s.WriteString(style.Render(line))
		s.WriteString("\n")
	}

	// Footer
	s.WriteString("\n")
	if m.message != "" {
		s.WriteString(m.message)
		s.WriteString("\n")
	}

	s.WriteString(infoStyle.Render(fmt.Sprintf("Total: %d archive(s) | Selected: %d/%d",
		len(m.archives), m.cursor+1, len(m.archives))))
	s.WriteString("\n")
	s.WriteString(infoStyle.Render("[KEY]  ↑/↓: Navigate | Enter: Select | s: Single DB | p: Profile | d: Diagnose | f: Filter | Esc: Back"))

	return s.String()
}

// filterArchives filters archives based on current filter setting
func (m ArchiveBrowserModel) filterArchives(archives []ArchiveInfo) []ArchiveInfo {
	if m.filterType == "all" {
		return archives
	}

	var filtered []ArchiveInfo
	for _, archive := range archives {
		switch m.filterType {
		case "postgres":
			if archive.Format.IsPostgreSQL() && !archive.Format.IsClusterBackup() {
				filtered = append(filtered, archive)
			}
		case "mysql":
			if archive.Format.IsMySQL() {
				filtered = append(filtered, archive)
			}
		case "cluster":
			if archive.Format.IsClusterBackup() {
				filtered = append(filtered, archive)
			}
		}
	}
	return filtered
}

// stripFileExtensions removes common backup file extensions from a name
func stripFileExtensions(name string) string {
	// Remove extensions (handle double extensions like .sql.gz.sql.gz)
	for {
		oldName := name
		name = strings.TrimSuffix(name, ".tar.gz")
		name = strings.TrimSuffix(name, ".dump.gz")
		name = strings.TrimSuffix(name, ".sql.gz")
		name = strings.TrimSuffix(name, ".dump")
		name = strings.TrimSuffix(name, ".sql")
		// If no change, we're done
		if name == oldName {
			break
		}
	}
	return name
}

// extractDBNameFromFilename extracts database name from archive filename
func extractDBNameFromFilename(filename string) string {
	base := filepath.Base(filename)

	// Remove extensions
	base = stripFileExtensions(base)

	// Remove timestamp patterns (YYYYMMDD_HHMMSS)
	parts := strings.Split(base, "_")
	for i := len(parts) - 1; i >= 0; i-- {
		// Check if part looks like a date or time
		if len(parts[i]) == 8 || len(parts[i]) == 6 {
			parts = parts[:i]
		} else {
			break
		}
	}

	if len(parts) > 0 {
		return parts[0]
	}

	return base
}

// formatSize formats file size
func formatSize(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// truncate truncates string to max length
func truncate(s string, max int) string {
	if len(s) <= max {
		return s
	}
	return s[:max-3] + "..."
}
