package wal

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

	"dbbackup/internal/logger"
)

// TimelineManager manages PostgreSQL timeline history and tracking
type TimelineManager struct {
	log logger.Logger
}

// NewTimelineManager creates a new timeline manager
func NewTimelineManager(log logger.Logger) *TimelineManager {
	return &TimelineManager{
		log: log,
	}
}

// TimelineInfo represents information about a PostgreSQL timeline
type TimelineInfo struct {
	TimelineID      uint32    // Timeline identifier (1, 2, 3, etc.)
	ParentTimeline  uint32    // Parent timeline ID (0 for timeline 1)
	SwitchPoint     string    // LSN where timeline switch occurred
	Reason          string    // Reason for timeline switch (from .history file)
	HistoryFile     string    // Path to .history file
	FirstWALSegment uint64    // First WAL segment in this timeline
	LastWALSegment  uint64    // Last known WAL segment in this timeline
	CreatedAt       time.Time // When timeline was created
}

// TimelineHistory represents the complete timeline branching structure
type TimelineHistory struct {
	Timelines       []*TimelineInfo          // All timelines sorted by ID
	CurrentTimeline uint32                   // Current active timeline
	TimelineMap     map[uint32]*TimelineInfo // Quick lookup by timeline ID
}

// ParseTimelineHistory parses timeline history from an archive directory
func (tm *TimelineManager) ParseTimelineHistory(ctx context.Context, archiveDir string) (*TimelineHistory, error) {
	tm.log.Info("Parsing timeline history", "archive_dir", archiveDir)

	history := &TimelineHistory{
		Timelines:   make([]*TimelineInfo, 0),
		TimelineMap: make(map[uint32]*TimelineInfo),
	}

	// Find all .history files in archive directory
	historyFiles, err := filepath.Glob(filepath.Join(archiveDir, "*.history"))
	if err != nil {
		return nil, fmt.Errorf("failed to find timeline history files: %w", err)
	}

	// Parse each history file
	for _, histFile := range historyFiles {
		timeline, err := tm.parseHistoryFile(histFile)
		if err != nil {
			tm.log.Warn("Failed to parse history file", "file", histFile, "error", err)
			continue
		}
		history.Timelines = append(history.Timelines, timeline)
		history.TimelineMap[timeline.TimelineID] = timeline
	}

	// Always add timeline 1 (base timeline) if not present
	if _, exists := history.TimelineMap[1]; !exists {
		baseTimeline := &TimelineInfo{
			TimelineID:      1,
			ParentTimeline:  0,
			SwitchPoint:     "0/0",
			Reason:          "Base timeline",
			FirstWALSegment: 0,
		}
		history.Timelines = append(history.Timelines, baseTimeline)
		history.TimelineMap[1] = baseTimeline
	}

	// Sort timelines by ID
	sort.Slice(history.Timelines, func(i, j int) bool {
		return history.Timelines[i].TimelineID < history.Timelines[j].TimelineID
	})

	// Scan WAL files to populate segment ranges
	if err := tm.scanWALSegments(archiveDir, history); err != nil {
		tm.log.Warn("Failed to scan WAL segments", "error", err)
	}

	// Determine current timeline (highest timeline ID with WAL files)
	for i := len(history.Timelines) - 1; i >= 0; i-- {
		if history.Timelines[i].LastWALSegment > 0 {
			history.CurrentTimeline = history.Timelines[i].TimelineID
			break
		}
	}
	if history.CurrentTimeline == 0 {
		history.CurrentTimeline = 1 // Default to timeline 1
	}

	tm.log.Info("Timeline history parsed",
		"timelines", len(history.Timelines),
		"current_timeline", history.CurrentTimeline)

	return history, nil
}

// parseHistoryFile parses a single .history file
// Format: <parentTLI> <switchpoint> <reason>
// Example: 00000001.history contains "1	0/3000000	no recovery target specified"
func (tm *TimelineManager) parseHistoryFile(path string) (*TimelineInfo, error) {
	// Extract timeline ID from filename (e.g., "00000002.history" -> 2)
	filename := filepath.Base(path)
	if !strings.HasSuffix(filename, ".history") {
		return nil, fmt.Errorf("invalid history file name: %s", filename)
	}

	timelineStr := strings.TrimSuffix(filename, ".history")
	timelineID64, err := strconv.ParseUint(timelineStr, 16, 32)
	if err != nil {
		return nil, fmt.Errorf("invalid timeline ID in filename %s: %w", filename, err)
	}
	timelineID := uint32(timelineID64)

	// Read file content
	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to open history file: %w", err)
	}
	defer file.Close()

	stat, err := file.Stat()
	if err != nil {
		return nil, fmt.Errorf("failed to stat history file: %w", err)
	}

	timeline := &TimelineInfo{
		TimelineID:  timelineID,
		HistoryFile: path,
		CreatedAt:   stat.ModTime(),
	}

	// Parse history entries (last line is the most recent)
	scanner := bufio.NewScanner(file)
	var lastLine string
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" && !strings.HasPrefix(line, "#") {
			lastLine = line
		}
	}

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

	// Parse the last line: "parentTLI switchpoint reason"
	if lastLine != "" {
		parts := strings.SplitN(lastLine, "\t", 3)
		if len(parts) >= 2 {
			// Parent timeline
			parentTLI64, err := strconv.ParseUint(strings.TrimSpace(parts[0]), 16, 32)
			if err == nil {
				timeline.ParentTimeline = uint32(parentTLI64)
			}

			// Switch point (LSN)
			timeline.SwitchPoint = strings.TrimSpace(parts[1])

			// Reason (optional)
			if len(parts) >= 3 {
				timeline.Reason = strings.TrimSpace(parts[2])
			}
		}
	}

	return timeline, nil
}

// scanWALSegments scans the archive directory to populate segment ranges for each timeline
func (tm *TimelineManager) scanWALSegments(archiveDir string, history *TimelineHistory) error {
	// Find all WAL files (including compressed/encrypted)
	patterns := []string{"*", "*.gz", "*.enc", "*.gz.enc"}
	walFiles := make([]string, 0)

	for _, pattern := range patterns {
		matches, err := filepath.Glob(filepath.Join(archiveDir, pattern))
		if err != nil {
			continue
		}
		walFiles = append(walFiles, matches...)
	}

	// Process each WAL file
	for _, walFile := range walFiles {
		filename := filepath.Base(walFile)

		// Remove extensions
		filename = strings.TrimSuffix(filename, ".gz.enc")
		filename = strings.TrimSuffix(filename, ".enc")
		filename = strings.TrimSuffix(filename, ".gz")

		// Skip non-WAL files
		if len(filename) != 24 {
			continue
		}

		// Parse WAL filename: TTTTTTTTXXXXXXXXYYYYYYYY
		// T = Timeline (8 hex), X = Log file (8 hex), Y = Segment (8 hex)
		timelineID64, err := strconv.ParseUint(filename[0:8], 16, 32)
		if err != nil {
			continue
		}
		timelineID := uint32(timelineID64)

		segmentID64, err := strconv.ParseUint(filename[8:24], 16, 64)
		if err != nil {
			continue
		}

		// Update timeline info
		if tl, exists := history.TimelineMap[timelineID]; exists {
			if tl.FirstWALSegment == 0 || segmentID64 < tl.FirstWALSegment {
				tl.FirstWALSegment = segmentID64
			}
			if segmentID64 > tl.LastWALSegment {
				tl.LastWALSegment = segmentID64
			}
		}
	}

	return nil
}

// ValidateTimelineConsistency validates that the timeline chain is consistent
func (tm *TimelineManager) ValidateTimelineConsistency(ctx context.Context, history *TimelineHistory) error {
	tm.log.Info("Validating timeline consistency", "timelines", len(history.Timelines))

	// Check that each timeline (except 1) has a valid parent
	for _, tl := range history.Timelines {
		if tl.TimelineID == 1 {
			continue // Base timeline has no parent
		}

		if tl.ParentTimeline == 0 {
			return fmt.Errorf("timeline %d has no parent timeline", tl.TimelineID)
		}

		parent, exists := history.TimelineMap[tl.ParentTimeline]
		if !exists {
			return fmt.Errorf("timeline %d references non-existent parent timeline %d",
				tl.TimelineID, tl.ParentTimeline)
		}

		// Verify parent timeline has WAL files up to the switch point
		if parent.LastWALSegment == 0 {
			tm.log.Warn("Parent timeline has no WAL segments",
				"timeline", tl.TimelineID,
				"parent", tl.ParentTimeline)
		}
	}

	tm.log.Info("Timeline consistency validated", "timelines", len(history.Timelines))
	return nil
}

// GetTimelinePath returns the path from timeline 1 to the target timeline
func (tm *TimelineManager) GetTimelinePath(history *TimelineHistory, targetTimeline uint32) ([]*TimelineInfo, error) {
	path := make([]*TimelineInfo, 0)

	currentTL := targetTimeline
	for currentTL > 0 {
		tl, exists := history.TimelineMap[currentTL]
		if !exists {
			return nil, fmt.Errorf("timeline %d not found in history", currentTL)
		}

		// Prepend to path (we're walking backwards)
		path = append([]*TimelineInfo{tl}, path...)

		// Move to parent
		if currentTL == 1 {
			break // Reached base timeline
		}
		currentTL = tl.ParentTimeline

		// Prevent infinite loops
		if len(path) > 100 {
			return nil, fmt.Errorf("timeline path too long (possible cycle)")
		}
	}

	return path, nil
}

// FindTimelineAtPoint finds which timeline was active at a given LSN
func (tm *TimelineManager) FindTimelineAtPoint(history *TimelineHistory, targetLSN string) (uint32, error) {
	// Start from current timeline and walk backwards
	for i := len(history.Timelines) - 1; i >= 0; i-- {
		tl := history.Timelines[i]

		// Compare LSNs (simplified - in production would need proper LSN comparison)
		if tl.SwitchPoint <= targetLSN || tl.SwitchPoint == "0/0" {
			return tl.TimelineID, nil
		}
	}

	// Default to timeline 1
	return 1, nil
}

// GetRequiredWALFiles returns all WAL files needed for recovery to a target timeline
func (tm *TimelineManager) GetRequiredWALFiles(ctx context.Context, history *TimelineHistory, archiveDir string, targetTimeline uint32) ([]string, error) {
	tm.log.Info("Finding required WAL files", "target_timeline", targetTimeline)

	// Get timeline path from base to target
	path, err := tm.GetTimelinePath(history, targetTimeline)
	if err != nil {
		return nil, fmt.Errorf("failed to get timeline path: %w", err)
	}

	requiredFiles := make([]string, 0)

	// Collect WAL files for each timeline in the path
	for _, tl := range path {
		// Find all WAL files for this timeline
		pattern := fmt.Sprintf("%08X*", tl.TimelineID)
		matches, err := filepath.Glob(filepath.Join(archiveDir, pattern))
		if err != nil {
			return nil, fmt.Errorf("failed to find WAL files for timeline %d: %w", tl.TimelineID, err)
		}

		requiredFiles = append(requiredFiles, matches...)

		// Also include the .history file
		historyFile := filepath.Join(archiveDir, fmt.Sprintf("%08X.history", tl.TimelineID))
		if _, err := os.Stat(historyFile); err == nil {
			requiredFiles = append(requiredFiles, historyFile)
		}
	}

	tm.log.Info("Required WAL files collected",
		"files", len(requiredFiles),
		"timelines", len(path))

	return requiredFiles, nil
}

// FormatTimelineTree returns a formatted string showing the timeline branching structure
func (tm *TimelineManager) FormatTimelineTree(history *TimelineHistory) string {
	if len(history.Timelines) == 0 {
		return "No timelines found"
	}

	var sb strings.Builder
	sb.WriteString("Timeline Branching Structure:\n")
	sb.WriteString("=============================\n\n")

	// Build tree recursively
	tm.formatTimelineNode(&sb, history, 1, 0, "")

	return sb.String()
}

// formatTimelineNode recursively formats a timeline node and its children
func (tm *TimelineManager) formatTimelineNode(sb *strings.Builder, history *TimelineHistory, timelineID uint32, depth int, prefix string) {
	tl, exists := history.TimelineMap[timelineID]
	if !exists {
		return
	}

	// Format current node
	indent := strings.Repeat("  ", depth)
	marker := "+-"
	if depth == 0 {
		marker = "*"
	}

	sb.WriteString(fmt.Sprintf("%s%s Timeline %d", indent, marker, tl.TimelineID))

	if tl.TimelineID == history.CurrentTimeline {
		sb.WriteString(" [CURRENT]")
	}

	if tl.SwitchPoint != "" && tl.SwitchPoint != "0/0" {
		sb.WriteString(fmt.Sprintf(" (switched at %s)", tl.SwitchPoint))
	}

	if tl.FirstWALSegment > 0 {
		sb.WriteString(fmt.Sprintf("\n%s   WAL segments: %d files", indent, tl.LastWALSegment-tl.FirstWALSegment+1))
	}

	if tl.Reason != "" {
		sb.WriteString(fmt.Sprintf("\n%s   Reason: %s", indent, tl.Reason))
	}

	sb.WriteString("\n")

	// Find and format children
	children := make([]*TimelineInfo, 0)
	for _, child := range history.Timelines {
		if child.ParentTimeline == timelineID {
			children = append(children, child)
		}
	}

	// Recursively format children
	for _, child := range children {
		tm.formatTimelineNode(sb, history, child.TimelineID, depth+1, prefix)
	}
}
