package tools

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"skraak/utils"
)

// CallsFromRavenInput defines the input for the calls-from-raven tool
type CallsFromRavenInput struct {
	Folder          string          `json:"folder"`
	File            string          `json:"file"`
	Delete          bool            `json:"delete"`
	ProgressHandler ProgressHandler `json:"-"` // Optional progress callback
}

// CallsFromRavenOutput defines the output for the calls-from-raven tool
type CallsFromRavenOutput struct {
	Calls            []ClusteredCall `json:"calls"`
	TotalCalls       int             `json:"total_calls"`
	SpeciesCount     map[string]int  `json:"species_count"`
	DataFilesWritten int             `json:"data_files_written"`
	DataFilesSkipped int             `json:"data_files_skipped"`
	FilesProcessed   int             `json:"files_processed"`
	FilesDeleted     int             `json:"files_deleted"`
	Filter           string          `json:"filter"`
	Error            *string         `json:"error,omitempty"`
}

// ravenSource implements CallSource for Raven selection files
type ravenSource struct{}

func (ravenSource) Name() string { return "Raven" }

func (ravenSource) FindFiles(folder string) ([]string, error) {
	var files []string

	entries, err := os.ReadDir(folder)
	if err != nil {
		return nil, err
	}

	for _, entry := range entries {
		name := entry.Name()
		if strings.HasSuffix(name, ".selections.txt") {
			files = append(files, filepath.Join(folder, name))
		}
	}

	return files, nil
}

func (ravenSource) ProcessFile(ravenFile string, cache *DirCache) ([]ClusteredCall, bool, bool, error) {
	return processRavenFileCached(ravenFile, cache)
}

// CallsFromRaven processes Raven selection files and writes .data files
func CallsFromRaven(input CallsFromRavenInput) (CallsFromRavenOutput, error) {
	src := ravenSource{}
	commonInput := CallsFromSourceInput(input)

	commonOutput, err := callsFromSource(src, commonInput)

	// Convert to Raven-specific output type
	var output CallsFromRavenOutput
	output.Calls = commonOutput.Calls
	output.TotalCalls = commonOutput.TotalCalls
	output.SpeciesCount = commonOutput.SpeciesCount
	output.DataFilesWritten = commonOutput.DataFilesWritten
	output.DataFilesSkipped = commonOutput.DataFilesSkipped
	output.FilesProcessed = commonOutput.FilesProcessed
	output.FilesDeleted = commonOutput.FilesDeleted
	output.Filter = commonOutput.Filter
	output.Error = commonOutput.Error
	return output, err
}

// RavenSelection represents a single Raven selection
type RavenSelection struct {
	StartTime float64
	EndTime   float64
	FreqLow   float64
	FreqHigh  float64
	Species   string
}

// ravenColumnIndices holds the column index positions for a Raven file
type ravenColumnIndices struct {
	beginTimeIdx int
	endTimeIdx   int
	lowFreqIdx   int
	highFreqIdx  int
	speciesIdx   int
}

// parseRavenHeader finds column indices from a tab-separated header line
func parseRavenHeader(header []string) (ravenColumnIndices, error) {
	idx := ravenColumnIndices{beginTimeIdx: -1, endTimeIdx: -1, lowFreqIdx: -1, highFreqIdx: -1, speciesIdx: -1}
	for i, col := range header {
		switch col {
		case "Begin Time (s)":
			idx.beginTimeIdx = i
		case "End Time (s)":
			idx.endTimeIdx = i
		case "Low Freq (Hz)":
			idx.lowFreqIdx = i
		case "High Freq (Hz)":
			idx.highFreqIdx = i
		case "Species":
			idx.speciesIdx = i
		}
	}
	if idx.beginTimeIdx == -1 || idx.endTimeIdx == -1 || idx.speciesIdx == -1 {
		return idx, fmt.Errorf("missing required columns in Raven file")
	}
	return idx, nil
}

// parseRavenSelections reads all selection rows from a scanner and returns parsed selections
func parseRavenSelections(scanner *bufio.Scanner, idx ravenColumnIndices) ([]RavenSelection, error) {
	var selections []RavenSelection
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" {
			continue
		}

		fields := strings.Split(line, "\t")
		if len(fields) <= idx.speciesIdx {
			continue
		}

		sel, err := parseRavenRow(fields, idx)
		if err != nil {
			return nil, err
		}
		selections = append(selections, sel)
	}
	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error reading file: %w", err)
	}
	return selections, nil
}

// parseRavenRow parses a single tab-separated row into a RavenSelection
func parseRavenRow(fields []string, idx ravenColumnIndices) (RavenSelection, error) {
	var sel RavenSelection
	startTime, err := strconv.ParseFloat(fields[idx.beginTimeIdx], 64)
	if err != nil {
		return sel, fmt.Errorf("failed to parse begin time %q: %w", fields[idx.beginTimeIdx], err)
	}
	sel.StartTime = startTime

	endTime, err := strconv.ParseFloat(fields[idx.endTimeIdx], 64)
	if err != nil {
		return sel, fmt.Errorf("failed to parse end time %q: %w", fields[idx.endTimeIdx], err)
	}
	sel.EndTime = endTime

	if idx.lowFreqIdx >= 0 && idx.lowFreqIdx < len(fields) {
		freqLow, err := strconv.ParseFloat(fields[idx.lowFreqIdx], 64)
		if err != nil {
			return sel, fmt.Errorf("failed to parse low freq %q: %w", fields[idx.lowFreqIdx], err)
		}
		sel.FreqLow = freqLow
	}
	if idx.highFreqIdx >= 0 && idx.highFreqIdx < len(fields) {
		freqHigh, err := strconv.ParseFloat(fields[idx.highFreqIdx], 64)
		if err != nil {
			return sel, fmt.Errorf("failed to parse high freq %q: %w", fields[idx.highFreqIdx], err)
		}
		sel.FreqHigh = freqHigh
	}
	sel.Species = fields[idx.speciesIdx]
	return sel, nil
}

// deriveWAVBaseName extracts the base WAV filename from a Raven .selections.txt filename
func deriveWAVBaseName(ravenFile string) string {
	base := filepath.Base(ravenFile)
	nameWithoutSuffix := strings.TrimSuffix(base, ".selections.txt")
	idx := strings.Index(nameWithoutSuffix, ".Table.")
	if idx > 0 {
		nameWithoutSuffix = nameWithoutSuffix[:idx]
	}
	return nameWithoutSuffix
}

// processRavenFileCached processes a single Raven selection file using a DirCache for WAV lookup
func processRavenFileCached(ravenFile string, cache *DirCache) ([]ClusteredCall, bool, bool, error) {
	file, err := os.Open(ravenFile)
	if err != nil {
		return nil, false, false, fmt.Errorf("failed to open file: %w", err)
	}
	defer func() { _ = file.Close() }()

	scanner := bufio.NewScanner(file)

	if !scanner.Scan() {
		return nil, false, false, fmt.Errorf("empty file")
	}
	header := strings.Split(scanner.Text(), "\t")

	idx, err := parseRavenHeader(header)
	if err != nil {
		return nil, false, false, err
	}

	selections, err := parseRavenSelections(scanner, idx)
	if err != nil {
		return nil, false, false, err
	}

	if len(selections) == 0 {
		return nil, false, true, nil
	}

	// Find WAV file
	wavPath := resolveWAVPath(ravenFile, cache)
	if wavPath == "" {
		return nil, false, true, nil
	}

	sampleRate, duration, err := utils.ParseWAVHeaderMinimal(wavPath)
	if err != nil {
		return nil, false, true, nil
	}

	dataPath := wavPath + ".data"
	segments := buildRavenSegments(selections, sampleRate)

	meta := AviaNZMeta{Operator: "Raven", Duration: duration}
	reviewer := "None"
	meta.Reviewer = &reviewer

	if err := writeDotDataFileSafe(dataPath, segments, "Raven", meta); err != nil {
		return nil, false, false, err
	}

	var calls []ClusteredCall
	for _, sel := range selections {
		calls = append(calls, ClusteredCall{
			File:      wavPath,
			StartTime: sel.StartTime,
			EndTime:   sel.EndTime,
			EbirdCode: sel.Species,
			Segments:  1,
		})
	}

	return calls, true, false, nil
}

// resolveWAVPath finds the WAV file corresponding to a Raven file
func resolveWAVPath(ravenFile string, cache *DirCache) string {
	baseName := deriveWAVBaseName(ravenFile)
	if cache != nil {
		return cache.FindWAV(baseName)
	}
	return findWAVFile(filepath.Dir(ravenFile), baseName)
}

// buildRavenSegments converts Raven selections to AviaNZ segments
func buildRavenSegments(selections []RavenSelection, sampleRate int) []AviaNZSegment {
	var segments []AviaNZSegment

	for _, sel := range selections {
		labels := []AviaNZLabel{
			{
				Species:   sel.Species,
				Certainty: 70, // Default certainty for Raven (no confidence metric)
				Filter:    "Raven",
			},
		}

		// Use frequency range from Raven, or full band if not specified
		freqLow := sel.FreqLow
		freqHigh := sel.FreqHigh
		if freqLow == 0 && freqHigh == 0 {
			freqHigh = float64(sampleRate)
		}

		segment := AviaNZSegment{
			sel.StartTime,
			sel.EndTime,
			freqLow,
			freqHigh,
			labels,
		}
		segments = append(segments, segment)
	}

	return segments
}