package restore

import (
	"encoding/json"
	"io"
	"os"
	"strings"

	"github.com/klauspost/pgzip"
)

// ArchiveFormat represents the type of backup archive
type ArchiveFormat string

const (
	FormatPostgreSQLDump   ArchiveFormat = "PostgreSQL Dump (.dump)"
	FormatPostgreSQLDumpGz ArchiveFormat = "PostgreSQL Dump Compressed (.dump.gz)"
	FormatPostgreSQLSQL    ArchiveFormat = "PostgreSQL SQL (.sql)"
	FormatPostgreSQLSQLGz  ArchiveFormat = "PostgreSQL SQL Compressed (.sql.gz)"
	FormatMySQLSQL         ArchiveFormat = "MySQL SQL (.sql)"
	FormatMySQLSQLGz       ArchiveFormat = "MySQL SQL Compressed (.sql.gz)"
	FormatClusterTarGz     ArchiveFormat = "Cluster Archive (.tar.gz)"
	FormatUnknown          ArchiveFormat = "Unknown"
)

// backupMetadata represents the structure of .meta.json files
type backupMetadata struct {
	DatabaseType string `json:"database_type"`
}

// readMetadataDBType reads the database_type from the .meta.json file if it exists
func readMetadataDBType(archivePath string) string {
	metaPath := archivePath + ".meta.json"
	data, err := os.ReadFile(metaPath)
	if err != nil {
		return ""
	}
	var meta backupMetadata
	if err := json.Unmarshal(data, &meta); err != nil {
		return ""
	}
	return strings.ToLower(meta.DatabaseType)
}

// DetectArchiveFormat detects the format of a backup archive from its filename and content
func DetectArchiveFormat(filename string) ArchiveFormat {
	lower := strings.ToLower(filename)

	// Check for cluster archives first (most specific)
	if strings.Contains(lower, "cluster") && strings.HasSuffix(lower, ".tar.gz") {
		return FormatClusterTarGz
	}

	// For .dump files, assume PostgreSQL custom format based on extension
	// If the file exists and can be read, verify with magic bytes
	if strings.HasSuffix(lower, ".dump.gz") {
		// Check if file exists and has content signature
		result := isCustomFormat(filename, true)
		// If file doesn't exist or we can't read it, trust the extension
		// If file exists and has PGDMP signature, it's custom format
		// If file exists but doesn't have signature, it might be SQL named as .dump
		if result == formatCheckCustom || result == formatCheckFileNotFound {
			return FormatPostgreSQLDumpGz
		}
		return FormatPostgreSQLSQLGz
	}

	if strings.HasSuffix(lower, ".dump") {
		result := isCustomFormat(filename, false)
		if result == formatCheckCustom || result == formatCheckFileNotFound {
			return FormatPostgreSQLDump
		}
		return FormatPostgreSQLSQL
	}

	// Check for compressed SQL formats
	if strings.HasSuffix(lower, ".sql.gz") {
		// First, try to determine from metadata file
		if dbType := readMetadataDBType(filename); dbType != "" {
			if dbType == "mysql" || dbType == "mariadb" {
				return FormatMySQLSQLGz
			}
			return FormatPostgreSQLSQLGz
		}
		// Fallback: determine if MySQL or PostgreSQL based on naming convention
		if strings.Contains(lower, "mysql") || strings.Contains(lower, "mariadb") {
			return FormatMySQLSQLGz
		}
		return FormatPostgreSQLSQLGz
	}

	// Check for uncompressed SQL formats
	if strings.HasSuffix(lower, ".sql") {
		// First, try to determine from metadata file
		if dbType := readMetadataDBType(filename); dbType != "" {
			if dbType == "mysql" || dbType == "mariadb" {
				return FormatMySQLSQLGz
			}
			return FormatPostgreSQLSQL
		}
		// Fallback: determine if MySQL or PostgreSQL based on naming convention
		if strings.Contains(lower, "mysql") || strings.Contains(lower, "mariadb") {
			return FormatMySQLSQL
		}
		return FormatPostgreSQLSQL
	}

	if strings.HasSuffix(lower, ".tar.gz") || strings.HasSuffix(lower, ".tgz") {
		return FormatClusterTarGz
	}

	return FormatUnknown
}

// formatCheckResult represents the result of checking file format
type formatCheckResult int

const (
	formatCheckFileNotFound formatCheckResult = iota
	formatCheckCustom
	formatCheckNotCustom
)

// isCustomFormat checks if a file is PostgreSQL custom format (has PGDMP signature)
func isCustomFormat(filename string, compressed bool) formatCheckResult {
	file, err := os.Open(filename)
	if err != nil {
		// File doesn't exist or can't be opened - return file not found
		return formatCheckFileNotFound
	}
	defer file.Close()

	var reader io.Reader = file

	// Handle compression
	if compressed {
		gz, err := pgzip.NewReader(file)
		if err != nil {
			return formatCheckFileNotFound
		}
		defer gz.Close()
		reader = gz
	}

	// Read first 5 bytes to check for PGDMP signature
	buffer := make([]byte, 5)
	n, err := reader.Read(buffer)
	if err != nil || n < 5 {
		return formatCheckNotCustom
	}

	if string(buffer) == "PGDMP" {
		return formatCheckCustom
	}
	return formatCheckNotCustom
}

// IsCompressed returns true if the archive format is compressed
func (f ArchiveFormat) IsCompressed() bool {
	return f == FormatPostgreSQLDumpGz ||
		f == FormatPostgreSQLSQLGz ||
		f == FormatMySQLSQLGz ||
		f == FormatClusterTarGz
}

// IsClusterBackup returns true if the archive is a cluster backup
func (f ArchiveFormat) IsClusterBackup() bool {
	return f == FormatClusterTarGz
}

// IsPostgreSQL returns true if the archive is PostgreSQL format
func (f ArchiveFormat) IsPostgreSQL() bool {
	return f == FormatPostgreSQLDump ||
		f == FormatPostgreSQLDumpGz ||
		f == FormatPostgreSQLSQL ||
		f == FormatPostgreSQLSQLGz ||
		f == FormatClusterTarGz
}

// IsMySQL returns true if format is MySQL
func (f ArchiveFormat) IsMySQL() bool {
	return f == FormatMySQLSQL || f == FormatMySQLSQLGz
}

// String returns human-readable format name
func (f ArchiveFormat) String() string {
	switch f {
	case FormatPostgreSQLDump:
		return "PostgreSQL Dump"
	case FormatPostgreSQLDumpGz:
		return "PostgreSQL Dump (gzip)"
	case FormatPostgreSQLSQL:
		return "PostgreSQL SQL"
	case FormatPostgreSQLSQLGz:
		return "PostgreSQL SQL (gzip)"
	case FormatMySQLSQL:
		return "MySQL SQL"
	case FormatMySQLSQLGz:
		return "MySQL SQL (gzip)"
	case FormatClusterTarGz:
		return "Cluster Archive (tar.gz)"
	default:
		return "Unknown"
	}
}
