package probers

import (
	"fmt"
	"strings"

	"github.com/asticode/go-astiav"
	"github.com/asticode/go-astikit"
	"github.com/flavioribeiro/donut/internal/entities"
	"github.com/flavioribeiro/donut/internal/mapper"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

type LibAVFFmpeg struct {
	c *entities.Config
	l *zap.SugaredLogger
	m *mapper.Mapper
}

type ResultLibAVFFmpeg struct {
	fx.Out
	LibAVFFmpegProber DonutProber `group:"probers"`
}

// NewLibAVFFmpeg creates a new LibAVFFmpeg DonutProber
func NewLibAVFFmpeg(
	c *entities.Config,
	l *zap.SugaredLogger,
	m *mapper.Mapper,
) ResultLibAVFFmpeg {
	return ResultLibAVFFmpeg{
		LibAVFFmpegProber: &LibAVFFmpeg{
			c: c,
			l: l,
			m: m,
		},
	}
}

// Match returns true when the request is for an LibAVFFmpeg prober
func (c *LibAVFFmpeg) Match(req *entities.RequestParams) bool {
	isRTMP := strings.Contains(strings.ToLower(req.StreamURL), "rtmp")
	isSRT := strings.Contains(strings.ToLower(req.StreamURL), "srt")

	return isRTMP || isSRT
}

// StreamInfo connects to the SRT stream to discovery media properties.
func (c *LibAVFFmpeg) StreamInfo(req entities.DonutAppetizer) (*entities.StreamInfo, error) {
	closer := astikit.NewCloser()
	defer closer.Close()

	var inputFormatContext *astiav.FormatContext
	if inputFormatContext = astiav.AllocFormatContext(); inputFormatContext == nil {
		return nil, entities.ErrFFmpegLibAVFormatContextIsNil
	}
	closer.Add(inputFormatContext.Free)

	inputFormat, err := c.defineInputFormat(req.Format.String())
	if err != nil {
		return nil, err
	}
	inputOptions := c.defineInputOptions(req.Options, closer)

	if err := inputFormatContext.OpenInput(req.URL, inputFormat, inputOptions); err != nil {
		return nil, fmt.Errorf("error while inputFormatContext.OpenInput: (%s, %#v, %#v) %w", req.URL, inputFormat, inputOptions, err)
	}
	closer.Add(inputFormatContext.CloseInput)

	if err := inputFormatContext.FindStreamInfo(nil); err != nil {
		return nil, fmt.Errorf("error while inputFormatContext.FindStreamInfo %w", err)
	}

	streams := []entities.Stream{}
	for _, is := range inputFormatContext.Streams() {
		if is.CodecParameters().MediaType() != astiav.MediaTypeAudio &&
			is.CodecParameters().MediaType() != astiav.MediaTypeVideo {
			c.l.Info("skipping media type", is.CodecParameters().MediaType())
			continue
		}
		streams = append(streams, c.m.FromLibAVStreamToEntityStream(is))
	}
	si := entities.StreamInfo{Streams: streams}

	return &si, nil
}

// TODO: merge common behavior (streamer / prober)
func (c *LibAVFFmpeg) defineInputFormat(streamFormat string) (*astiav.InputFormat, error) {
	var inputFormat *astiav.InputFormat
	if streamFormat != "" {
		inputFormat = astiav.FindInputFormat(streamFormat)
		if inputFormat == nil {
			return nil, fmt.Errorf("ffmpeg/libav: could not find %s input format", streamFormat)
		}
	}
	return inputFormat, nil
}

func (c *LibAVFFmpeg) defineInputOptions(opts map[entities.DonutInputOptionKey]string, closer *astikit.Closer) *astiav.Dictionary {
	var dic *astiav.Dictionary
	if len(opts) > 0 {
		dic = &astiav.Dictionary{}
		closer.Add(dic.Free)

		for k, v := range opts {
			dic.Set(k.String(), v, 0)
		}
	}
	return dic
}
