package utils

import (
	"database/sql"
	"fmt"
	"path/filepath"
	"time"
)

// TimestampResult holds the result of timestamp resolution for a single file
type TimestampResult struct {
	Timestamp   time.Time
	IsAudioMoth bool
	MothData    *AudioMothData
}

// ResolveTimestamp resolves a file's timestamp using the standard priority chain:
// 1. AudioMoth comment parsing
// 2. Filename timestamp parsing + timezone offset
// 3. File modification time (if useFileModTime is true)
//
// Returns an error if no timestamp could be determined.
func ResolveTimestamp(wavMeta *WAVMetadata, filePath string, timezoneID string, useFileModTime bool) (*TimestampResult, error) {
	result := &TimestampResult{}

	// Step 1: Try AudioMoth comment
	if IsAudioMoth(wavMeta.Comment, wavMeta.Artist) {
		result.IsAudioMoth = true
		mothData, err := ParseAudioMothComment(wavMeta.Comment)
		if err == nil {
			result.MothData = mothData
			result.Timestamp = mothData.Timestamp
			return result, nil
		}
		// AudioMoth detected but parsing failed — fall through to filename
	}

	// Step 2: Try filename timestamp
	if HasTimestampFilename(filePath) {
		filenameTimestamps, err := ParseFilenameTimestamps([]string{filepath.Base(filePath)})
		if err == nil {
			adjustedTimestamps, err := ApplyTimezoneOffset(filenameTimestamps, timezoneID)
			if err == nil && len(adjustedTimestamps) > 0 {
				result.Timestamp = adjustedTimestamps[0]
				return result, nil
			}
		}
	}

	// Step 3: File modification time fallback (optional)
	if useFileModTime && !wavMeta.FileModTime.IsZero() {
		result.Timestamp = wavMeta.FileModTime
		return result, nil
	}

	return nil, fmt.Errorf("cannot resolve timestamp (no AudioMoth, filename pattern, or file modification time)")
}

// FileProcessingResult holds all extracted metadata for a single file
type FileProcessingResult struct {
	FileName       string
	Hash           string
	Duration       float64
	SampleRate     int
	TimestampLocal time.Time
	IsAudioMoth    bool
	MothData       *AudioMothData
	AstroData      AstronomicalData
}

// ProcessSingleFile runs the full single-file processing pipeline:
// WAV header parsing → XXH64 hash → timestamp resolution → astronomical data
//
// Set useFileModTime to true to allow file modification time as a timestamp fallback.
func ProcessSingleFile(filePath string, latitude, longitude float64, timezoneID string, useFileModTime bool) (*FileProcessingResult, error) {
	// Step 1: Parse WAV header
	metadata, err := ParseWAVHeader(filePath)
	if err != nil {
		return nil, fmt.Errorf("WAV header parsing failed: %w", err)
	}

	// Step 2: Calculate hash
	hash, err := ComputeXXH64(filePath)
	if err != nil {
		return nil, fmt.Errorf("hash calculation failed: %w", err)
	}

	// Step 3: Resolve timestamp
	tsResult, err := ResolveTimestamp(metadata, filePath, timezoneID, useFileModTime)
	if err != nil {
		return nil, err
	}

	// Step 4: Calculate astronomical data
	astroData := CalculateAstronomicalData(
		tsResult.Timestamp.UTC(),
		metadata.Duration,
		latitude,
		longitude,
	)

	return &FileProcessingResult{
		FileName:       filepath.Base(filePath),
		Hash:           hash,
		Duration:       metadata.Duration,
		SampleRate:     metadata.SampleRate,
		TimestampLocal: tsResult.Timestamp,
		IsAudioMoth:    tsResult.IsAudioMoth,
		MothData:       tsResult.MothData,
		AstroData:      astroData,
	}, nil
}

// DBQueryable is an interface satisfied by both *sql.DB and *sql.Tx
// for running duplicate hash checks against either.
type DBQueryable interface {
	QueryRow(query string, args ...interface{}) *sql.Row
}

// CheckDuplicateHash checks if a file with the given XXH64 hash already exists.
// Returns the existing file ID if found, or empty string if no duplicate.
// Works with both *sql.DB and *sql.Tx.
func CheckDuplicateHash(q DBQueryable, hash string) (existingID string, isDuplicate bool, err error) {
	err = q.QueryRow(
		"SELECT id FROM file WHERE xxh64_hash = ? AND active = true",
		hash,
	).Scan(&existingID)

	if err == nil {
		return existingID, true, nil
	}
	if err == sql.ErrNoRows {
		return "", false, nil
	}
	return "", false, fmt.Errorf("duplicate check failed: %w", err)
}