package utils

import (
	"fmt"
	"path/filepath"
	"regexp"
	"strconv"
	"time"
)

// DateFormat represents the detected filename date format
type DateFormat int

const (
	Format8Digit DateFormat = iota // YYYYMMDD_HHMMSS (e.g., 20230609_103000.wav)
	Format6YYMMDD                  // YYMMDD_HHMMSS (e.g., 201012_123456.wav) - year first
	Format6DDMMYY                  // DDMMYY_HHMMSS (e.g., 121020_123456.wav) - year last
)

var (
	// Pattern to match timestamp filenames
	// Supports: YYYYMMDD_HHMMSS, YYMMDD_HHMMSS, DDMMYY_HHMMSS
	timestampPattern = regexp.MustCompile(`^(\d{6,8})_(\d{6})\.wav$`)
)

// dateParts represents parsed date components for format detection
type dateParts struct {
	x1 int // First 2 digits
	m  int // Middle 2 digits (always month)
	x2 int // Last 2 digits
}

// FilenameTimestamp represents a parsed timestamp from a filename
type FilenameTimestamp struct {
	Filename  string
	Timestamp time.Time
	Format    DateFormat
}

// ParseFilenameTimestamps parses timestamps from a batch of filenames.
// Uses variance-based disambiguation for 6-digit dates (YYMMDD vs DDMMYY).
// Returns timestamps in UTC (timezone must be applied separately).
func ParseFilenameTimestamps(filenames []string) ([]FilenameTimestamp, error) {
	if len(filenames) == 0 {
		return nil, fmt.Errorf("no filenames provided")
	}

	// Detect date format by analyzing all filenames
	format, err := detectDateFormat(filenames)
	if err != nil {
		return nil, err
	}

	// Parse all filenames using detected format
	results := make([]FilenameTimestamp, 0, len(filenames))
	for _, filename := range filenames {
		timestamp, err := parseFilenameWithFormat(filename, format)
		if err != nil {
			return nil, fmt.Errorf("failed to parse %s: %w", filename, err)
		}
		results = append(results, FilenameTimestamp{
			Filename:  filename,
			Timestamp: timestamp,
			Format:    format,
		})
	}

	return results, nil
}

// ApplyTimezoneOffset applies a fixed timezone offset to timestamps
// Uses the first timestamp to determine the offset, then applies it to all
// This matches AudioMoth behavior (no DST adjustment during deployment)
func ApplyTimezoneOffset(timestamps []FilenameTimestamp, timezoneID string) ([]time.Time, error) {
	if len(timestamps) == 0 {
		return nil, fmt.Errorf("no timestamps provided")
	}

	// Load timezone location
	loc, err := time.LoadLocation(timezoneID)
	if err != nil {
		return nil, fmt.Errorf("invalid timezone %s: %w", timezoneID, err)
	}

	// Calculate offset from first timestamp
	firstUTC := timestamps[0].Timestamp
	firstInZone := time.Date(
		firstUTC.Year(), firstUTC.Month(), firstUTC.Day(),
		firstUTC.Hour(), firstUTC.Minute(), firstUTC.Second(),
		0, loc,
	)

	// Get fixed offset (doesn't change for DST)
	_, offsetSeconds := firstInZone.Zone()
	fixedOffset := time.FixedZone("Fixed", offsetSeconds)

	// Apply SAME offset to ALL timestamps
	results := make([]time.Time, len(timestamps))
	for i, ts := range timestamps {
		adjusted := time.Date(
			ts.Timestamp.Year(), ts.Timestamp.Month(), ts.Timestamp.Day(),
			ts.Timestamp.Hour(), ts.Timestamp.Minute(), ts.Timestamp.Second(),
			0, fixedOffset,
		)
		results[i] = adjusted
	}

	return results, nil
}

// detectDateFormat analyzes filenames to determine the date format
func detectDateFormat(filenames []string) (DateFormat, error) {
	// Extract all date parts from filenames
	var parts []dateParts
	var has8Digit bool

	for _, filename := range filenames {
		basename := filepath.Base(filename)
		matches := timestampPattern.FindStringSubmatch(basename)
		if matches == nil {
			continue
		}

		dateStr := matches[1]

		// Check for 8-digit format (YYYYMMDD)
		if len(dateStr) == 8 {
			has8Digit = true
			continue
		}

		// Parse 6-digit format
		if len(dateStr) == 6 {
			x1, _ := strconv.Atoi(dateStr[0:2])
			m, _ := strconv.Atoi(dateStr[2:4])
			x2, _ := strconv.Atoi(dateStr[4:6])
			parts = append(parts, dateParts{x1: x1, m: m, x2: x2})
		}
	}

	// If all files are 8-digit, that's the format
	if has8Digit && len(parts) == 0 {
		return Format8Digit, nil
	}

	// If mixed 8-digit and 6-digit, prefer 8-digit
	if has8Digit {
		return Format8Digit, nil
	}

	// If no 6-digit dates found, cannot determine
	if len(parts) == 0 {
		return 0, fmt.Errorf("no valid timestamp filenames found")
	}

	// Use variance-based disambiguation for 6-digit dates
	// Compare uniqueness of x1 (first 2 digits) vs x2 (last 2 digits)
	// Day values vary more than year values across recordings
	uniqueX1 := countUnique(parts, func(p dateParts) int { return p.x1 })
	uniqueX2 := countUnique(parts, func(p dateParts) int { return p.x2 })

	if uniqueX2 >= uniqueX1 {
		// x2 has more variance → likely day values → YYMMDD format
		return Format6YYMMDD, nil
	} else {
		// x1 has more variance → likely day values → DDMMYY format
		return Format6DDMMYY, nil
	}
}

// parseFilenameWithFormat parses a filename using the specified format
func parseFilenameWithFormat(filename string, format DateFormat) (time.Time, error) {
	basename := filepath.Base(filename)
	matches := timestampPattern.FindStringSubmatch(basename)
	if matches == nil {
		return time.Time{}, fmt.Errorf("filename does not match timestamp pattern: %s", basename)
	}

	dateStr := matches[1]
	timeStr := matches[2]

	var year, month, day int

	switch format {
	case Format8Digit:
		if len(dateStr) != 8 {
			return time.Time{}, fmt.Errorf("expected 8-digit date, got %d digits", len(dateStr))
		}
		year, _ = strconv.Atoi(dateStr[0:4])
		month, _ = strconv.Atoi(dateStr[4:6])
		day, _ = strconv.Atoi(dateStr[6:8])

	case Format6YYMMDD:
		if len(dateStr) != 6 {
			return time.Time{}, fmt.Errorf("expected 6-digit date, got %d digits", len(dateStr))
		}
		yy, _ := strconv.Atoi(dateStr[0:2])
		month, _ = strconv.Atoi(dateStr[2:4])
		day, _ = strconv.Atoi(dateStr[4:6])
		// Convert 2-digit year to 4-digit (assume 2000-2099)
		year = 2000 + yy

	case Format6DDMMYY:
		if len(dateStr) != 6 {
			return time.Time{}, fmt.Errorf("expected 6-digit date, got %d digits", len(dateStr))
		}
		day, _ = strconv.Atoi(dateStr[0:2])
		month, _ = strconv.Atoi(dateStr[2:4])
		yy, _ := strconv.Atoi(dateStr[4:6])
		// Convert 2-digit year to 4-digit (assume 2000-2099)
		year = 2000 + yy
	}

	// Parse time (HHMMSS)
	if len(timeStr) != 6 {
		return time.Time{}, fmt.Errorf("invalid time format: %s", timeStr)
	}
	hour, _ := strconv.Atoi(timeStr[0:2])
	minute, _ := strconv.Atoi(timeStr[2:4])
	second, _ := strconv.Atoi(timeStr[4:6])

	// Construct timestamp in UTC (timezone applied separately)
	timestamp := time.Date(year, time.Month(month), day, hour, minute, second, 0, time.UTC)

	// Validate date
	if timestamp.Month() != time.Month(month) || timestamp.Day() != day {
		return time.Time{}, fmt.Errorf("invalid date: %04d-%02d-%02d", year, month, day)
	}

	return timestamp, nil
}

// countUnique counts unique values using an extractor function
func countUnique(parts []dateParts, extractor func(p dateParts) int) int {
	seen := make(map[int]bool)
	for _, p := range parts {
		seen[extractor(p)] = true
	}
	return len(seen)
}

// HasTimestampFilename checks if a filename matches the timestamp pattern
func HasTimestampFilename(filename string) bool {
	basename := filepath.Base(filename)
	return timestampPattern.MatchString(basename)
}