package processing

import (
	"context"
	"fmt"
	"log"
	"m3u8-downloader/pkg/constants"
	"m3u8-downloader/pkg/nas"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"sync"
)

type ProcessingService struct {
	config *ProcessConfig
	nas    *nas.NASService
}

func NewProcessingService(eventName string) (*ProcessingService, error) {
	config := &ProcessConfig{
		WorkerCount:     constants.ProcessWorkerCount,
		SourcePath:      constants.NASOutputPath + "/" + eventName,
		DestinationPath: constants.ProcessOutputPath,
		Enabled:         constants.ProcessingEnabled,
	}

	nasConfig := &nas.NASConfig{
		Path:       constants.NASOutputPath,
		Username:   constants.NASUsername,
		Password:   constants.NASPassword,
		Timeout:    constants.TransferTimeout,
		RetryLimit: constants.TransferRetryLimit,
		VerifySize: true,
	}

	nasService := nas.NewNASService(*nasConfig)

	if err := nasService.TestConnection(); err != nil {
		return nil, fmt.Errorf("Failed to connect to NAS: %w", err)
	}

	return &ProcessingService{
		config: config,
		nas:    nasService,
	}, nil
}

func (ps *ProcessingService) GetSegmentInfo() (map[int]string, error) {
	return nil, nil
}

func (ps *ProcessingService) Start(ctx context.Context) error {
	if !ps.config.Enabled {
		log.Println("Processing service disabled")
		return nil
	}

	//Get all present resolutions
	dirs, err := ps.GetResolutions()
	if err != nil {
		return fmt.Errorf("Failed to get resolutions: %w", err)
	}

	//Spawn a worker per resolution
	ch := make(chan SegmentInfo, 100)
	var wg sync.WaitGroup

	for _, resolution := range dirs {
		wg.Add(1)
		go ps.ParseResolutionDirectory(resolution, ch, &wg)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	segments, err := ps.AggregateSegmentInfo(ch)
	if err != nil {
		return fmt.Errorf("Failed to aggregate segment info: %w", err)
	}

	ps.WriteConcatFile(segments)

	//Feed info to ffmpeg to stitch files together
	concatErr := ps.RunFFmpeg(ps.config.SourcePath, ps.config.DestinationPath)
	if concatErr != nil {
		return concatErr
	}

	return nil
}

func (ps *ProcessingService) GetResolutions() ([]string, error) {
	dirs, err := os.ReadDir(ps.config.SourcePath)
	if err != nil {
		return nil, fmt.Errorf("Failed to read source directory: %w", err)
	}

	re := regexp.MustCompile(`^\d+p$`)

	var resolutions []string
	for _, dir := range dirs {
		if dir.IsDir() && re.MatchString(dir.Name()) {
			resolutions = append(resolutions, dir.Name())
		}
	}

	return resolutions, nil
}

func (ps *ProcessingService) ParseResolutionDirectory(resolution string, ch chan<- SegmentInfo, wg *sync.WaitGroup) {
	defer wg.Done()

	files, err := os.ReadDir(ps.config.SourcePath + "/" + resolution)
	if err != nil {
		log.Printf("Failed to read resolution directory: %v", err)
		return
	}

	for _, file := range files {
		if !file.IsDir() {
			no, err := strconv.Atoi(file.Name()[7:11])
			if err != nil {
				log.Printf("Failed to parse segment number: %v", err)
				continue
			}
			ch <- SegmentInfo{
				Name:       file.Name(),
				SeqNo:      no,
				Resolution: resolution,
			}
		}
	}
}

func (ps *ProcessingService) AggregateSegmentInfo(ch <-chan SegmentInfo) (map[int]SegmentInfo, error) {
	segmentMap := make(map[int]SegmentInfo)

	rank := map[string]int{
		"1080p": 1,
		"720p":  2,
		"540p":  3,
		"480p":  4,
		"450p":  5,
		"360p":  6,
		"270p":  7,
		"240p":  8,
	}

	for segment := range ch {
		current, exists := segmentMap[segment.SeqNo]
		if !exists || rank[segment.Resolution] > rank[current.Resolution] {
			segmentMap[segment.SeqNo] = segment
		}
	}

	return segmentMap, nil
}

func (ps *ProcessingService) WriteConcatFile(segmentMap map[int]SegmentInfo) error {
	f, err := os.Create(ps.config.DestinationPath + "/concat.txt")
	if err != nil {
		return fmt.Errorf("Failed to create concat file: %w", err)
	}
	defer f.Close()

	for _, segment := range segmentMap {
		_, err = f.WriteString("file '" + ps.config.SourcePath + "/" + segment.Resolution + "/" + segment.Name + "'\n")
		if err != nil {
			return fmt.Errorf("Failed to write to concat file: %w", err)
		}
	}

	return nil
}

func (ps *ProcessingService) RunFFmpeg(inputPath, outputPath string) error {
	cmd := exec.Command("ffmpeg", "-f", "concat", "-safe", "0", "-i", inputPath, "-c", "copy", outputPath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("Failed to run ffmpeg: %w", err)
	}
	return nil
}
