package metadata

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"
)

// BackupMetadata contains comprehensive information about a backup
type BackupMetadata struct {
	Version         string            `json:"version"`
	Timestamp       time.Time         `json:"timestamp"`
	Database        string            `json:"database"`
	DatabaseType    string            `json:"database_type"`    // postgresql, mysql, mariadb
	DatabaseVersion string            `json:"database_version"` // e.g., "PostgreSQL 15.3"
	Host            string            `json:"host"`
	Port            int               `json:"port"`
	User            string            `json:"user"`
	BackupFile      string            `json:"backup_file"`
	SizeBytes       int64             `json:"size_bytes"`
	SHA256          string            `json:"sha256"`
	Compression     string            `json:"compression"` // none, gzip, pigz
	BackupType      string            `json:"backup_type"` // full, incremental (for v2.2)
	BaseBackup      string            `json:"base_backup,omitempty"`
	Duration        float64           `json:"duration_seconds"`
	ExtraInfo       map[string]string `json:"extra_info,omitempty"`

	// Encryption fields (v2.3+)
	Encrypted           bool   `json:"encrypted"`                      // Whether backup is encrypted
	EncryptionAlgorithm string `json:"encryption_algorithm,omitempty"` // e.g., "aes-256-gcm"

	// Incremental backup fields (v2.2+)
	Incremental *IncrementalMetadata `json:"incremental,omitempty"` // Only present for incremental backups
}

// IncrementalMetadata contains metadata specific to incremental backups
type IncrementalMetadata struct {
	BaseBackupID        string    `json:"base_backup_id"`        // SHA-256 of base backup
	BaseBackupPath      string    `json:"base_backup_path"`      // Filename of base backup
	BaseBackupTimestamp time.Time `json:"base_backup_timestamp"` // When base backup was created
	IncrementalFiles    int       `json:"incremental_files"`     // Number of changed files
	TotalSize           int64     `json:"total_size"`            // Total size of changed files (bytes)
	BackupChain         []string  `json:"backup_chain"`          // Chain: [base, incr1, incr2, ...]
}

// ClusterMetadata contains metadata for cluster backups
type ClusterMetadata struct {
	Version      string            `json:"version"`
	Timestamp    time.Time         `json:"timestamp"`
	ClusterName  string            `json:"cluster_name"`
	DatabaseType string            `json:"database_type"`
	Host         string            `json:"host"`
	Port         int               `json:"port"`
	Databases    []BackupMetadata  `json:"databases"`
	TotalSize    int64             `json:"total_size_bytes"`
	Duration     float64           `json:"duration_seconds"`
	ExtraInfo    map[string]string `json:"extra_info,omitempty"`
}

// CalculateSHA256 computes the SHA-256 checksum of a file
func CalculateSHA256(filePath string) (string, error) {
	f, err := os.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("failed to open file: %w", err)
	}
	defer f.Close()

	hasher := sha256.New()
	if _, err := io.Copy(hasher, f); err != nil {
		return "", fmt.Errorf("failed to calculate checksum: %w", err)
	}

	return hex.EncodeToString(hasher.Sum(nil)), nil
}

// Save writes metadata to a .meta.json file
func (m *BackupMetadata) Save() error {
	metaPath := m.BackupFile + ".meta.json"

	data, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	if err := os.WriteFile(metaPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write metadata file: %w", err)
	}

	return nil
}

// Load reads metadata from a .meta.json file
func Load(backupFile string) (*BackupMetadata, error) {
	metaPath := backupFile + ".meta.json"

	data, err := os.ReadFile(metaPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read metadata file: %w", err)
	}

	var meta BackupMetadata
	if err := json.Unmarshal(data, &meta); err != nil {
		return nil, fmt.Errorf("failed to parse metadata: %w", err)
	}

	return &meta, nil
}

// SaveCluster writes cluster metadata to a .meta.json file
func (m *ClusterMetadata) Save(targetFile string) error {
	metaPath := targetFile + ".meta.json"

	data, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal cluster metadata: %w", err)
	}

	if err := os.WriteFile(metaPath, data, 0644); err != nil {
		return fmt.Errorf("failed to write cluster metadata file: %w", err)
	}

	return nil
}

// LoadCluster reads cluster metadata from a .meta.json file
func LoadCluster(targetFile string) (*ClusterMetadata, error) {
	metaPath := targetFile + ".meta.json"

	data, err := os.ReadFile(metaPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read cluster metadata file: %w", err)
	}

	var meta ClusterMetadata
	if err := json.Unmarshal(data, &meta); err != nil {
		return nil, fmt.Errorf("failed to parse cluster metadata: %w", err)
	}

	return &meta, nil
}

// ListBackups scans a directory for backup files and returns their metadata
func ListBackups(dir string) ([]*BackupMetadata, error) {
	pattern := filepath.Join(dir, "*.meta.json")
	matches, err := filepath.Glob(pattern)
	if err != nil {
		return nil, fmt.Errorf("failed to scan directory: %w", err)
	}

	var backups []*BackupMetadata
	for _, metaFile := range matches {
		// Extract backup file path (remove .meta.json suffix)
		backupFile := metaFile[:len(metaFile)-len(".meta.json")]

		meta, err := Load(backupFile)
		if err != nil {
			// Skip invalid metadata files
			continue
		}

		backups = append(backups, meta)
	}

	return backups, nil
}

// FormatSize returns human-readable 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 %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
