package backend

import (
	"fmt"
	"math"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/go-flac/go-flac"
	mewflac "github.com/mewkiz/flac"
)

type AnalysisResult struct {
	FilePath      string        `json:"file_path"`
	FileSize      int64         `json:"file_size"`
	SampleRate    uint32        `json:"sample_rate"`
	Channels      uint8         `json:"channels"`
	BitsPerSample uint8         `json:"bits_per_sample"`
	TotalSamples  uint64        `json:"total_samples"`
	Duration      float64       `json:"duration"`
	Bitrate       int           `json:"bit_rate"`
	BitDepth      string        `json:"bit_depth"`
	DynamicRange  float64       `json:"dynamic_range"`
	PeakAmplitude float64       `json:"peak_amplitude"`
	RMSLevel      float64       `json:"rms_level"`
	Spectrum      *SpectrumData `json:"spectrum,omitempty"`
}

func AnalyzeTrack(filepath string) (*AnalysisResult, error) {
	if !fileExists(filepath) {
		return nil, fmt.Errorf("file does not exist: %s", filepath)
	}

	fileInfo, err := os.Stat(filepath)
	if err != nil {
		return nil, fmt.Errorf("failed to get file info: %w", err)
	}

	f, err := flac.ParseFile(filepath)
	if err != nil {
		return nil, fmt.Errorf("failed to parse FLAC file: %w", err)
	}

	result := &AnalysisResult{
		FilePath: filepath,
		FileSize: fileInfo.Size(),
	}

	if len(f.Meta) > 0 {
		streamInfo := f.Meta[0]
		if streamInfo.Type == flac.StreamInfo {

			data := streamInfo.Data
			if len(data) >= 18 {

				result.SampleRate = uint32(data[10])<<12 | uint32(data[11])<<4 | uint32(data[12])>>4

				result.Channels = ((data[12] >> 1) & 0x07) + 1

				result.BitsPerSample = ((data[12]&0x01)<<4 | data[13]>>4) + 1

				result.TotalSamples = uint64(data[13]&0x0F)<<32 |
					uint64(data[14])<<24 |
					uint64(data[15])<<16 |
					uint64(data[16])<<8 |
					uint64(data[17])

				if result.SampleRate > 0 {
					result.Duration = float64(result.TotalSamples) / float64(result.SampleRate)
				}

			}
		}
	}

	spectrum, err := AnalyzeSpectrum(filepath)
	if err != nil {

		fmt.Printf("Warning: failed to analyze spectrum: %v\n", err)
	} else {
		result.Spectrum = spectrum

		calculateRealAudioMetrics(result, filepath)
	}

	result.BitDepth = fmt.Sprintf("%d-bit", result.BitsPerSample)

	return result, nil
}

func calculateRealAudioMetrics(result *AnalysisResult, filepath string) {

	samples, err := decodeFLACForMetrics(filepath)
	if err != nil {
		return
	}

	var peak float64
	var sumSquares float64

	for _, sample := range samples {
		absVal := sample
		if absVal < 0 {
			absVal = -absVal
		}
		if absVal > peak {
			peak = absVal
		}
		sumSquares += sample * sample
	}

	peakDB := 20.0 * math.Log10(peak)
	result.PeakAmplitude = peakDB

	rms := math.Sqrt(sumSquares / float64(len(samples)))
	rmsDB := 20.0 * math.Log10(rms)
	result.RMSLevel = rmsDB

	result.DynamicRange = peakDB - rmsDB
}

func decodeFLACForMetrics(filepath string) ([]float64, error) {
	stream, err := mewflac.ParseFile(filepath)
	if err != nil {
		return nil, err
	}
	defer stream.Close()

	maxSamples := 10000000
	samples := make([]float64, 0, maxSamples)

	for {
		frame, err := stream.ParseNext()
		if err != nil {
			break
		}

		var channelSamples []int32
		if len(frame.Subframes) > 0 {
			channelSamples = frame.Subframes[0].Samples
		}

		maxVal := float64(int64(1) << (stream.Info.BitsPerSample - 1))
		for _, sample := range channelSamples {
			if len(samples) >= maxSamples {
				return samples, nil
			}
			normalized := float64(sample) / maxVal
			samples = append(samples, normalized)
		}

		if len(samples) >= maxSamples {
			break
		}
	}

	return samples, nil
}

func GetFileSize(filepath string) (int64, error) {
	info, err := os.Stat(filepath)
	if err != nil {
		return 0, err
	}
	return info.Size(), nil
}

func GetTrackMetadata(filepath string) (*AnalysisResult, error) {
	if !fileExists(filepath) {
		return nil, fmt.Errorf("file does not exist: %s", filepath)
	}

	return GetMetadataWithFFprobe(filepath)
}

func GetMetadataWithFFprobe(filePath string) (*AnalysisResult, error) {
	ffprobePath, err := GetFFprobePath()
	if err != nil {
		return nil, err
	}

	for i := 0; i < 5; i++ {
		if f, err := os.Open(filePath); err == nil {
			f.Close()
			break
		}
		time.Sleep(200 * time.Millisecond)
	}

	args := []string{
		"-v", "error",
		"-select_streams", "a:0",
		"-show_entries", "stream=sample_rate,channels,bits_per_raw_sample,bits_per_sample,duration,bit_rate",
		"-of", "default=noprint_wrappers=1:nokey=1",
		filePath,
	}

	cmd := exec.Command(ffprobePath, args...)
	setHideWindow(cmd)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("ffprobe failed: %w - %s", err, string(output))
	}

	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	if len(lines) < 4 {
		return nil, fmt.Errorf("unexpected ffprobe output: %s", string(output))
	}

	res := &AnalysisResult{
		FilePath: filePath,
	}

	if info, err := os.Stat(filePath); err == nil {
		res.FileSize = info.Size()
	}

	infoMap := make(map[string]string)

	args = []string{
		"-v", "error",
		"-select_streams", "a:0",
		"-show_entries", "stream=sample_rate,channels,bits_per_raw_sample,bits_per_sample,duration,bit_rate",
		"-of", "default=noprint_wrappers=0",
		filePath,
	}
	cmd = exec.Command(ffprobePath, args...)
	setHideWindow(cmd)
	output, err = cmd.CombinedOutput()
	if err == nil {
		lines = strings.Split(string(output), "\n")
		for _, line := range lines {
			if strings.Contains(line, "=") {
				parts := strings.SplitN(line, "=", 2)
				infoMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
			}
		}
	}

	if val, ok := infoMap["sample_rate"]; ok {
		s, _ := strconv.Atoi(val)
		res.SampleRate = uint32(s)
	}
	if val, ok := infoMap["channels"]; ok {
		c, _ := strconv.Atoi(val)
		res.Channels = uint8(c)
	}
	if val, ok := infoMap["duration"]; ok {
		d, _ := strconv.ParseFloat(val, 64)
		res.Duration = d
	}
	if val, ok := infoMap["bit_rate"]; ok && val != "N/A" {
		br, _ := strconv.Atoi(val)
		res.Bitrate = br
	}

	bits := 0
	if val, ok := infoMap["bits_per_raw_sample"]; ok && val != "N/A" {
		bits, _ = strconv.Atoi(val)
	}
	if bits == 0 {
		if val, ok := infoMap["bits_per_sample"]; ok && val != "N/A" {
			bits, _ = strconv.Atoi(val)
		}
	}

	res.BitsPerSample = uint8(bits)
	if bits > 0 {
		res.BitDepth = fmt.Sprintf("%d-bit", bits)
	} else {
		res.BitDepth = "Unknown"
	}

	return res, nil
}
