package pitr

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// RecoveryTarget represents a PostgreSQL recovery target
type RecoveryTarget struct {
	Type      string // "time", "xid", "lsn", "name", "immediate"
	Value     string // The target value (timestamp, XID, LSN, or restore point name)
	Action    string // "promote", "pause", "shutdown"
	Timeline  string // Timeline to follow ("latest" or timeline ID)
	Inclusive bool   // Whether target is inclusive (default: true)
}

// RecoveryTargetType constants
const (
	TargetTypeTime      = "time"
	TargetTypeXID       = "xid"
	TargetTypeLSN       = "lsn"
	TargetTypeName      = "name"
	TargetTypeImmediate = "immediate"
)

// RecoveryAction constants
const (
	ActionPromote  = "promote"
	ActionPause    = "pause"
	ActionShutdown = "shutdown"
)

// ParseRecoveryTarget creates a RecoveryTarget from CLI flags
func ParseRecoveryTarget(
	targetTime, targetXID, targetLSN, targetName string,
	targetImmediate bool,
	targetAction, timeline string,
	inclusive bool,
) (*RecoveryTarget, error) {
	rt := &RecoveryTarget{
		Action:    targetAction,
		Timeline:  timeline,
		Inclusive: inclusive,
	}

	// Validate action
	if rt.Action == "" {
		rt.Action = ActionPromote // Default
	}
	if !isValidAction(rt.Action) {
		return nil, fmt.Errorf("invalid recovery action: %s (must be promote, pause, or shutdown)", rt.Action)
	}

	// Determine target type (only one can be specified)
	targetsSpecified := 0
	if targetTime != "" {
		rt.Type = TargetTypeTime
		rt.Value = targetTime
		targetsSpecified++
	}
	if targetXID != "" {
		rt.Type = TargetTypeXID
		rt.Value = targetXID
		targetsSpecified++
	}
	if targetLSN != "" {
		rt.Type = TargetTypeLSN
		rt.Value = targetLSN
		targetsSpecified++
	}
	if targetName != "" {
		rt.Type = TargetTypeName
		rt.Value = targetName
		targetsSpecified++
	}
	if targetImmediate {
		rt.Type = TargetTypeImmediate
		rt.Value = "immediate"
		targetsSpecified++
	}

	if targetsSpecified == 0 {
		return nil, fmt.Errorf("no recovery target specified (use --target-time, --target-xid, --target-lsn, --target-name, or --target-immediate)")
	}
	if targetsSpecified > 1 {
		return nil, fmt.Errorf("multiple recovery targets specified, only one allowed")
	}

	// Validate the target
	if err := rt.Validate(); err != nil {
		return nil, err
	}

	return rt, nil
}

// Validate validates the recovery target configuration
func (rt *RecoveryTarget) Validate() error {
	if rt.Type == "" {
		return fmt.Errorf("recovery target type not specified")
	}

	switch rt.Type {
	case TargetTypeTime:
		return rt.validateTime()
	case TargetTypeXID:
		return rt.validateXID()
	case TargetTypeLSN:
		return rt.validateLSN()
	case TargetTypeName:
		return rt.validateName()
	case TargetTypeImmediate:
		// Immediate has no value to validate
		return nil
	default:
		return fmt.Errorf("unknown recovery target type: %s", rt.Type)
	}
}

// validateTime validates a timestamp target
func (rt *RecoveryTarget) validateTime() error {
	if rt.Value == "" {
		return fmt.Errorf("recovery target time is empty")
	}

	// Try parsing various timestamp formats
	formats := []string{
		"2006-01-02 15:04:05",        // Standard format
		"2006-01-02 15:04:05.999999", // With microseconds
		"2006-01-02T15:04:05",        // ISO 8601
		"2006-01-02T15:04:05Z",       // ISO 8601 with UTC
		"2006-01-02T15:04:05-07:00",  // ISO 8601 with timezone
		time.RFC3339,                 // RFC3339
		time.RFC3339Nano,             // RFC3339 with nanoseconds
	}

	var parseErr error
	for _, format := range formats {
		_, err := time.Parse(format, rt.Value)
		if err == nil {
			return nil // Successfully parsed
		}
		parseErr = err
	}

	return fmt.Errorf("invalid timestamp format '%s': %w (expected format: YYYY-MM-DD HH:MM:SS)", rt.Value, parseErr)
}

// validateXID validates a transaction ID target
func (rt *RecoveryTarget) validateXID() error {
	if rt.Value == "" {
		return fmt.Errorf("recovery target XID is empty")
	}

	// XID must be a positive integer
	xid, err := strconv.ParseUint(rt.Value, 10, 64)
	if err != nil {
		return fmt.Errorf("invalid transaction ID '%s': must be a positive integer", rt.Value)
	}

	if xid == 0 {
		return fmt.Errorf("invalid transaction ID 0: XID must be greater than 0")
	}

	return nil
}

// validateLSN validates a Log Sequence Number target
func (rt *RecoveryTarget) validateLSN() error {
	if rt.Value == "" {
		return fmt.Errorf("recovery target LSN is empty")
	}

	// LSN format: XXX/XXXXXXXX (hex/hex)
	// Example: 0/3000000, 1/A2000000
	lsnPattern := regexp.MustCompile(`^[0-9A-Fa-f]+/[0-9A-Fa-f]+$`)
	if !lsnPattern.MatchString(rt.Value) {
		return fmt.Errorf("invalid LSN format '%s': expected format XXX/XXXXXXXX (e.g., 0/3000000)", rt.Value)
	}

	// Validate both parts are valid hex
	parts := strings.Split(rt.Value, "/")
	if len(parts) != 2 {
		return fmt.Errorf("invalid LSN format '%s': must contain exactly one '/'", rt.Value)
	}

	for i, part := range parts {
		if _, err := strconv.ParseUint(part, 16, 64); err != nil {
			return fmt.Errorf("invalid LSN component %d '%s': must be hexadecimal", i+1, part)
		}
	}

	return nil
}

// validateName validates a restore point name target
func (rt *RecoveryTarget) validateName() error {
	if rt.Value == "" {
		return fmt.Errorf("recovery target name is empty")
	}

	// PostgreSQL restore point names have some restrictions
	// They should be valid identifiers
	if len(rt.Value) > 63 {
		return fmt.Errorf("restore point name too long: %d characters (max 63)", len(rt.Value))
	}

	// Check for invalid characters (only alphanumeric, underscore, hyphen)
	validName := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
	if !validName.MatchString(rt.Value) {
		return fmt.Errorf("invalid restore point name '%s': only alphanumeric, underscore, and hyphen allowed", rt.Value)
	}

	return nil
}

// isValidAction checks if the recovery action is valid
func isValidAction(action string) bool {
	switch strings.ToLower(action) {
	case ActionPromote, ActionPause, ActionShutdown:
		return true
	default:
		return false
	}
}

// ToPostgreSQLConfig converts the recovery target to PostgreSQL configuration parameters
// Returns a map of config keys to values suitable for postgresql.auto.conf or recovery.conf
func (rt *RecoveryTarget) ToPostgreSQLConfig() map[string]string {
	config := make(map[string]string)

	// Set recovery target based on type
	switch rt.Type {
	case TargetTypeTime:
		config["recovery_target_time"] = rt.Value
	case TargetTypeXID:
		config["recovery_target_xid"] = rt.Value
	case TargetTypeLSN:
		config["recovery_target_lsn"] = rt.Value
	case TargetTypeName:
		config["recovery_target_name"] = rt.Value
	case TargetTypeImmediate:
		config["recovery_target"] = "immediate"
	}

	// Set recovery target action
	config["recovery_target_action"] = rt.Action

	// Set timeline
	if rt.Timeline != "" {
		config["recovery_target_timeline"] = rt.Timeline
	} else {
		config["recovery_target_timeline"] = "latest"
	}

	// Set inclusive flag (only for time, xid, lsn targets)
	if rt.Type != TargetTypeImmediate && rt.Type != TargetTypeName {
		if rt.Inclusive {
			config["recovery_target_inclusive"] = "true"
		} else {
			config["recovery_target_inclusive"] = "false"
		}
	}

	return config
}

// FormatConfigLine formats a config key-value pair for PostgreSQL config files
func FormatConfigLine(key, value string) string {
	// Quote values that contain spaces or special characters
	needsQuoting := strings.ContainsAny(value, " \t#'\"\\")
	if needsQuoting {
		// Escape single quotes
		value = strings.ReplaceAll(value, "'", "''")
		return fmt.Sprintf("%s = '%s'", key, value)
	}
	return fmt.Sprintf("%s = %s", key, value)
}

// String returns a human-readable representation of the recovery target
func (rt *RecoveryTarget) String() string {
	var sb strings.Builder

	sb.WriteString("Recovery Target:\n")
	sb.WriteString(fmt.Sprintf("  Type:      %s\n", rt.Type))

	if rt.Type != TargetTypeImmediate {
		sb.WriteString(fmt.Sprintf("  Value:     %s\n", rt.Value))
	}

	sb.WriteString(fmt.Sprintf("  Action:    %s\n", rt.Action))

	if rt.Timeline != "" {
		sb.WriteString(fmt.Sprintf("  Timeline:  %s\n", rt.Timeline))
	}

	if rt.Type != TargetTypeImmediate && rt.Type != TargetTypeName {
		sb.WriteString(fmt.Sprintf("  Inclusive: %v\n", rt.Inclusive))
	}

	return sb.String()
}

// Summary returns a one-line summary of the recovery target
func (rt *RecoveryTarget) Summary() string {
	switch rt.Type {
	case TargetTypeTime:
		return fmt.Sprintf("Restore to time: %s", rt.Value)
	case TargetTypeXID:
		return fmt.Sprintf("Restore to transaction ID: %s", rt.Value)
	case TargetTypeLSN:
		return fmt.Sprintf("Restore to LSN: %s", rt.Value)
	case TargetTypeName:
		return fmt.Sprintf("Restore to named point: %s", rt.Value)
	case TargetTypeImmediate:
		return "Restore to earliest consistent point"
	default:
		return "Unknown recovery target"
	}
}
