package engine

import (
	"fmt"
	"strings"

	"github.com/flavioribeiro/donut/internal/controllers/probers"
	"github.com/flavioribeiro/donut/internal/controllers/streamers"
	"github.com/flavioribeiro/donut/internal/entities"
	"github.com/flavioribeiro/donut/internal/mapper"
	"go.uber.org/fx"
)

type DonutEngine interface {
	Appetizer() (entities.DonutAppetizer, error)
	ServerIngredients() (*entities.StreamInfo, error)
	ClientIngredients() (*entities.StreamInfo, error)
	RecipeFor(server, client *entities.StreamInfo) (*entities.DonutRecipe, error)
	Serve(p *entities.DonutParameters)
}

type DonutEngineParams struct {
	fx.In
	Streamers []streamers.DonutStreamer `group:"streamers"`
	Probers   []probers.DonutProber     `group:"probers"`
	Mapper    *mapper.Mapper
}

type DonutEngineController struct {
	p DonutEngineParams
}

func NewDonutEngineController(p DonutEngineParams) *DonutEngineController {
	return &DonutEngineController{p}
}

func (c *DonutEngineController) EngineFor(req *entities.RequestParams) (DonutEngine, error) {
	prober := c.selectProberFor(req)
	if prober == nil {
		return nil, fmt.Errorf("request %v: not fulfilled. error %w", req, entities.ErrMissingProber)
	}

	streamer := c.selectStreamerFor(req)
	if streamer == nil {
		return nil, fmt.Errorf("request %v: not fulfilled. error %w", req, entities.ErrMissingStreamer)
	}

	return &donutEngine{
		prober:   prober,
		streamer: streamer,
		mapper:   c.p.Mapper,
		req:      req,
	}, nil
}

// TODO: try to use generics
func (c *DonutEngineController) selectProberFor(req *entities.RequestParams) probers.DonutProber {
	for _, p := range c.p.Probers {
		if p.Match(req) {
			return p
		}
	}
	return nil
}

// TODO: try to use generics
func (c *DonutEngineController) selectStreamerFor(req *entities.RequestParams) streamers.DonutStreamer {
	for _, p := range c.p.Streamers {
		if p.Match(req) {
			return p
		}
	}
	return nil
}

type donutEngine struct {
	prober   probers.DonutProber
	streamer streamers.DonutStreamer
	mapper   *mapper.Mapper
	req      *entities.RequestParams
}

func (d *donutEngine) ServerIngredients() (*entities.StreamInfo, error) {
	appetizer, err := d.Appetizer()
	if err != nil {
		return nil, err
	}
	return d.prober.StreamInfo(appetizer)
}

func (d *donutEngine) ClientIngredients() (*entities.StreamInfo, error) {
	return d.mapper.FromWebRTCSessionDescriptionToStreamInfo(d.req.Offer)
}

func (d *donutEngine) Serve(p *entities.DonutParameters) {
	d.streamer.Stream(p)
}

func (d *donutEngine) RecipeFor(server, client *entities.StreamInfo) (*entities.DonutRecipe, error) {
	// TODO: implement proper matching
	//
	// suggestions:
	//  if client.medias.contains(server.media)
	//     bypass, server.media
	//  else
	//     preferable = [vp8, opus]
	//     if union(preferable, client.medias)
	//         transcode, preferable
	appetizer, err := d.Appetizer()
	if err != nil {
		return nil, err
	}

	r := &entities.DonutRecipe{
		Input: appetizer,
		Video: entities.DonutMediaTask{
			// Action:               entities.DonutTranscode,
			Action:               entities.DonutBypass,
			Codec:                entities.H264,
			DonutBitStreamFilter: &entities.DonutH264AnnexB,
			// CodecContextOptions: []entities.LibAVOptionsCodecContext{
			// 	entities.SetBitRate(100_000),
			// 	entities.SetBaselineProfile(),
			// 	entities.SetGopSize(30),
			// },
		},
		Audio: entities.DonutMediaTask{
			Action:            entities.DonutTranscode,
			Codec:             entities.Opus,
			DonutStreamFilter: entities.AudioResamplerFilter(48000),
			CodecContextOptions: []entities.LibAVOptionsCodecContext{
				entities.SetSampleRate(48000),
				entities.SetSampleFormat("fltp"),
			},
		},
	}

	return r, nil
}

func (d *donutEngine) Appetizer() (entities.DonutAppetizer, error) {
	isRTMP := strings.Contains(strings.ToLower(d.req.StreamURL), "rtmp")
	isSRT := strings.Contains(strings.ToLower(d.req.StreamURL), "srt")

	if isRTMP {
		return entities.DonutAppetizer{
			URL: fmt.Sprintf("%s/%s", d.req.StreamURL, d.req.StreamID),
			Options: map[entities.DonutInputOptionKey]string{
				entities.DonutRTMPLive: "live",
			},
			Format: "flv",
		}, nil
	}

	if isSRT {
		return entities.DonutAppetizer{
			URL:    d.req.StreamURL,
			Format: "mpegts", // TODO: check how to get format for srt
			Options: map[entities.DonutInputOptionKey]string{
				entities.DonutSRTStreamID:  d.req.StreamID,
				entities.DonutSRTTranstype: "live",
				entities.DonutSRTsmoother:  "live",
			},
		}, nil
	}

	return entities.DonutAppetizer{}, entities.ErrUnsupportedStreamURL
}
