package services

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"go.uber.org/zap"
)

// AudioFormat represents supported audio output formats
type AudioFormat string

const (
	FormatMP3  AudioFormat = "mp3"
	FormatAAC  AudioFormat = "aac"
	FormatFLAC AudioFormat = "flac"
	FormatOGG  AudioFormat = "ogg"
	FormatWAV  AudioFormat = "wav"
	FormatM4A  AudioFormat = "m4a"
)

// AudioQuality represents quality presets
type AudioQuality string

const (
	QualityLow      AudioQuality = "low"      // 64-96 kbps
	QualityMedium   AudioQuality = "medium"   // 128-192 kbps
	QualityHigh     AudioQuality = "high"     // 256-320 kbps
	QualityLossless AudioQuality = "lossless" // FLAC/WAV
)

// TranscodeOptions represents options for audio transcoding
type TranscodeOptions struct {
	Format     AudioFormat   // Output format (default: MP3)
	Bitrate    int           // Bitrate in kbps (0 = use quality preset)
	Quality    AudioQuality  // Quality preset (overrides bitrate if set)
	SampleRate int           // Sample rate in Hz (0 = keep original)
	Channels   int           // Number of channels (0 = keep original)
	OutputPath string        // Output file path
	Timeout    time.Duration // Processing timeout (default: 5 minutes)
}

// TranscodeResult represents the result of audio transcoding
type TranscodeResult struct {
	OutputPath     string        `json:"output_path"`
	Format         AudioFormat   `json:"format"`
	Bitrate        int           `json:"bitrate"`
	SampleRate     int           `json:"sample_rate"`
	Channels       int           `json:"channels"`
	Duration       time.Duration `json:"duration"`
	FileSize       int64         `json:"file_size"`
	ProcessingTime time.Duration `json:"processing_time"`
}

// AudioTranscodeService provides audio transcoding capabilities
// BE-SVC-011: Implement audio transcoding service
type AudioTranscodeService struct {
	ffmpegPath string
	logger     *zap.Logger
}

// NewAudioTranscodeService creates a new audio transcoding service
func NewAudioTranscodeService(ffmpegPath string, logger *zap.Logger) *AudioTranscodeService {
	if ffmpegPath == "" {
		ffmpegPath = "ffmpeg" // Default to system PATH
	}
	if logger == nil {
		logger = zap.NewNop()
	}
	return &AudioTranscodeService{
		ffmpegPath: ffmpegPath,
		logger:     logger,
	}
}

// IsFFmpegAvailable checks if FFmpeg is available
func (s *AudioTranscodeService) IsFFmpegAvailable(ctx context.Context) bool {
	cmd := exec.CommandContext(ctx, s.ffmpegPath, "-version")
	if err := cmd.Run(); err != nil {
		s.logger.Warn("FFmpeg not available", zap.Error(err))
		return false
	}
	return true
}

// Transcode transcodes an audio file with the given options
func (s *AudioTranscodeService) Transcode(
	ctx context.Context,
	inputPath string,
	options TranscodeOptions,
) (*TranscodeResult, error) {
	// Validate input file
	if _, err := os.Stat(inputPath); os.IsNotExist(err) {
		return nil, fmt.Errorf("input file does not exist: %s", inputPath)
	}

	// Check FFmpeg availability
	if !s.IsFFmpegAvailable(ctx) {
		return nil, fmt.Errorf("ffmpeg is not available")
	}

	// Set defaults
	if options.Format == "" {
		options.Format = FormatMP3
	}
	if options.Timeout == 0 {
		options.Timeout = 5 * time.Minute
	}

	// Determine output path
	outputPath := options.OutputPath
	if outputPath == "" {
		// Generate output path based on input
		ext := filepath.Ext(inputPath)
		base := strings.TrimSuffix(inputPath, ext)
		outputPath = fmt.Sprintf("%s_%s.%s", base, string(options.Format), string(options.Format))
	}

	// Ensure output directory exists
	if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
		return nil, fmt.Errorf("failed to create output directory: %w", err)
	}

	// Determine bitrate from quality preset if not specified
	bitrate := options.Bitrate
	if bitrate == 0 && options.Quality != "" {
		bitrate = s.getBitrateForQuality(options.Quality, options.Format)
	}
	if bitrate == 0 {
		bitrate = 192 // Default bitrate
	}

	// Build FFmpeg command
	args := s.buildFFmpegArgs(inputPath, outputPath, options, bitrate)

	// Create context with timeout
	transcodeCtx, cancel := context.WithTimeout(ctx, options.Timeout)
	defer cancel()

	// Execute FFmpeg
	startTime := time.Now()
	cmd := exec.CommandContext(transcodeCtx, s.ffmpegPath, args...)

	// Capture stderr for logging
	var stderr strings.Builder
	cmd.Stderr = &stderr

	err := cmd.Run()
	processingTime := time.Since(startTime)

	if err != nil {
		s.logger.Error("FFmpeg transcoding failed",
			zap.String("input", inputPath),
			zap.String("output", outputPath),
			zap.String("format", string(options.Format)),
			zap.Int("bitrate", bitrate),
			zap.String("stderr", stderr.String()),
			zap.Error(err),
			zap.Duration("processing_time", processingTime),
		)
		return nil, fmt.Errorf("ffmpeg transcoding failed: %w", err)
	}

	// Verify output file exists
	fileInfo, err := os.Stat(outputPath)
	if err != nil {
		return nil, fmt.Errorf("output file was not created: %w", err)
	}

	// Get audio metadata (sample rate, channels)
	sampleRate, channels := s.getAudioMetadata(transcodeCtx, outputPath)

	result := &TranscodeResult{
		OutputPath:     outputPath,
		Format:         options.Format,
		Bitrate:        bitrate,
		SampleRate:     sampleRate,
		Channels:       channels,
		FileSize:       fileInfo.Size(),
		ProcessingTime: processingTime,
	}

	s.logger.Info("Audio transcoding completed",
		zap.String("input", inputPath),
		zap.String("output", outputPath),
		zap.String("format", string(options.Format)),
		zap.Int("bitrate", bitrate),
		zap.Int64("file_size", fileInfo.Size()),
		zap.Duration("processing_time", processingTime),
	)

	return result, nil
}

// buildFFmpegArgs builds FFmpeg command arguments
func (s *AudioTranscodeService) buildFFmpegArgs(
	inputPath, outputPath string,
	options TranscodeOptions,
	bitrate int,
) []string {
	args := []string{
		"-i", inputPath,
		"-y", // Overwrite output file
	}

	// Set codec based on format
	codec := s.getCodecForFormat(options.Format)
	if codec != "" {
		args = append(args, "-codec:a", codec)
	}

	// Set bitrate
	if options.Format != FormatFLAC && options.Format != FormatWAV {
		args = append(args, "-b:a", fmt.Sprintf("%dk", bitrate))
	}

	// Set sample rate
	if options.SampleRate > 0 {
		args = append(args, "-ar", fmt.Sprintf("%d", options.SampleRate))
	}

	// Set channels
	if options.Channels > 0 {
		args = append(args, "-ac", fmt.Sprintf("%d", options.Channels))
	}

	// Format-specific options
	switch options.Format {
	case FormatFLAC:
		args = append(args, "-compression_level", "5")
	case FormatOGG:
		args = append(args, "-q:a", "5") // Vorbis quality (0-10)
	}

	// Output file
	args = append(args, outputPath)

	return args
}

// getCodecForFormat returns the FFmpeg codec name for a format
func (s *AudioTranscodeService) getCodecForFormat(format AudioFormat) string {
	switch format {
	case FormatMP3:
		return "libmp3lame"
	case FormatAAC, FormatM4A:
		return "aac"
	case FormatFLAC:
		return "flac"
	case FormatOGG:
		return "libvorbis"
	case FormatWAV:
		return "pcm_s16le"
	default:
		return ""
	}
}

// getBitrateForQuality returns bitrate for a quality preset
func (s *AudioTranscodeService) getBitrateForQuality(quality AudioQuality, format AudioFormat) int {
	if format == FormatFLAC || format == FormatWAV {
		return 0 // Lossless formats don't use bitrate
	}

	switch quality {
	case QualityLow:
		if format == FormatAAC || format == FormatM4A {
			return 64
		}
		return 96
	case QualityMedium:
		if format == FormatAAC || format == FormatM4A {
			return 128
		}
		return 192
	case QualityHigh:
		if format == FormatAAC || format == FormatM4A {
			return 256
		}
		return 320
	case QualityLossless:
		return 0 // Lossless
	default:
		return 192 // Default
	}
}

// getAudioMetadata extracts audio metadata using ffprobe
func (s *AudioTranscodeService) getAudioMetadata(ctx context.Context, filePath string) (sampleRate, channels int) {
	// Try to use ffprobe if available
	ffprobePath := strings.Replace(s.ffmpegPath, "ffmpeg", "ffprobe", 1)
	cmd := exec.CommandContext(ctx, ffprobePath,
		"-v", "error",
		"-show_entries", "stream=sample_rate,channels",
		"-of", "default=noprint_wrappers=1:nokey=1",
		filePath,
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		s.logger.Warn("Failed to get audio metadata", zap.Error(err))
		return 44100, 2 // Default values
	}

	// Parse output (format: sample_rate\nchannels\n)
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	if len(lines) >= 2 {
		fmt.Sscanf(lines[0], "%d", &sampleRate)
		fmt.Sscanf(lines[1], "%d", &channels)
	}

	if sampleRate == 0 {
		sampleRate = 44100
	}
	if channels == 0 {
		channels = 2
	}

	return sampleRate, channels
}

// TranscodeMultiple transcodes an audio file to multiple formats/qualities
func (s *AudioTranscodeService) TranscodeMultiple(
	ctx context.Context,
	inputPath string,
	optionsList []TranscodeOptions,
) ([]*TranscodeResult, error) {
	var results []*TranscodeResult
	var errors []error

	for _, options := range optionsList {
		result, err := s.Transcode(ctx, inputPath, options)
		if err != nil {
			errors = append(errors, fmt.Errorf("transcode failed for format %s: %w", options.Format, err))
			continue
		}
		results = append(results, result)
	}

	if len(errors) > 0 && len(results) == 0 {
		return nil, fmt.Errorf("all transcoding operations failed: %v", errors)
	}

	if len(errors) > 0 {
		s.logger.Warn("Some transcoding operations failed",
			zap.Int("successful", len(results)),
			zap.Int("failed", len(errors)),
		)
	}

	return results, nil
}

// GetSupportedFormats returns list of supported output formats
func (s *AudioTranscodeService) GetSupportedFormats() []AudioFormat {
	return []AudioFormat{
		FormatMP3,
		FormatAAC,
		FormatFLAC,
		FormatOGG,
		FormatWAV,
		FormatM4A,
	}
}

// ValidateFormat checks if a format is supported
func (s *AudioTranscodeService) ValidateFormat(format AudioFormat) bool {
	supported := s.GetSupportedFormats()
	for _, f := range supported {
		if f == format {
			return true
		}
	}
	return false
}
