package ytdl

import (
	"bytes"
	"io"
	"os/exec"
	"strings"

	"go.fifitido.net/ytdl-web/config"
	"go.fifitido.net/ytdl-web/pkg/ytdl/cache"
	"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
	"golang.org/x/exp/slog"
)

type Ytdl interface {
	GetMetadata(url string) (*metadata.Metadata, error)
	Download(w io.Writer, url, format string, index int) error
	Version() string
}

type ytdlImpl struct {
	cfg     *config.Config
	logger  *slog.Logger
	cache   cache.MetadataCache
	version string
}

func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache) Ytdl {
	cmd := exec.Command(
		cfg.BinaryPath,
		"--version",
	)
	var out bytes.Buffer
	cmd.Stdout = &out
	_ = cmd.Run()

	return &ytdlImpl{
		cfg:     cfg,
		logger:  logger.With(slog.String("module", "ytdl")),
		cache:   cache,
		version: strings.TrimSpace(out.String()),
	}
}

func (y *ytdlImpl) baseOptions(url string) []Option {
	options := []Option{}

	metadata, err := y.cache.Get(url)
	if err == nil {
		options = append(options, WithLoadJson(metadata))
	}

	if y.cfg.Cookies.Enabled {
		if y.cfg.Cookies.FromBrowser.Browser != "" {
			options = append(options, WithBrowserCookies(
				y.cfg.Cookies.FromBrowser.Browser,
				y.cfg.Cookies.FromBrowser.Keyring,
				y.cfg.Cookies.FromBrowser.Profile,
				y.cfg.Cookies.FromBrowser.Container,
			))
		} else {
			options = append(options, WithCookieFile(y.cfg.Cookies.FilePath))
		}
	}

	if y.cfg.IsDevelopment() {
		options = append(options, WithDebug())
	}

	return options
}

func (y *ytdlImpl) Version() string {
	return y.version
}

// GetMetadata implements Ytdl
func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
	meta, err := y.cache.Get(url)
	if err == nil {
		return meta, nil
	}

	meta = &metadata.Metadata{}
	options := append(
		y.baseOptions(url),
		WithDumpJson(meta),
	)

	if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
		y.logger.Error("failed to get metadata", slog.String("url", url), slog.String("error", err.Error()))
		return nil, err
	}

	if err := y.cache.Set(url, meta, y.cfg.Cache.TTL); err != nil {
		y.logger.Warn("failed to cache metadata", slog.String("url", url), slog.String("error", err.Error()))
	}

	return meta, nil
}

// Download implements Ytdl
func (y *ytdlImpl) Download(w io.Writer, url, format string, index int) error {
	options := append(
		y.baseOptions(url),
		WithFormat(format),
		WithStreamOutput(w),
	)

	if index >= 0 {
		options = append(options, WithPlaylistIndex(index))
	}

	if err := Exec(y.cfg.BinaryPath, url, options...); err != nil {
		return err
	}

	return nil
}
