package choose_files

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/hako/durafmt"
	"github.com/kovidgoyal/imaging/magick"
	"github.com/kovidgoyal/kitty/tools/icons"
	"github.com/kovidgoyal/kitty/tools/utils/humanize"
	"github.com/kovidgoyal/kitty/tools/utils/images"
	"golang.org/x/sys/unix"
)

var _ = fmt.Print

const FFMPEG_METADATA_KEY = "ffmpeg-metadata.json"

type ffmpeg_renderer int

var (
	video_width            = 480
	video_fps              = 10
	video_duration         = 5.0
	video_encoding_quality = 75
)

func ffmpeg_thumbnail_cmd(path, outpath string) *exec.Cmd {
	return exec.Command(
		"ffmpeg", "-loglevel", "fatal", "-y", "-an", "-sn", "-dn", "-i", path, "-t", fmt.Sprintf("%f", video_duration),
		"-vf", fmt.Sprintf("fps=%d,scale=%d:-1:flags=lanczos", video_fps, video_width),
		"-c:v", "libwebp", "-lossless", "0", "-compression_level", "0", "-q:v",
		fmt.Sprintf("%d", video_encoding_quality), "-loop", "0", "-f", "webp", outpath,
	)
}

func ffmpeg_thumbnail(path, tempath string, wg *sync.WaitGroup) (ans *images.ImageData, err error) {
	defer wg.Done()
	cmd := ffmpeg_thumbnail_cmd(path, tempath)
	cmd.Stdin = nil
	cmd.SysProcAttr = &unix.SysProcAttr{Setsid: true}
	var stderr bytes.Buffer
	cmd.Stdout = nil
	cmd.Stderr = &stderr
	if err = cmd.Run(); err != nil {
		return ans, fmt.Errorf("failed to use ffmpeg to render video from %s with error: %w and stderr: %s", path, err, stderr.String())
	}
	ans, err = images.OpenImageFromPath(tempath)
	return
}

type FFMpegFormat struct {
	Start_time string         `json:"start_time"`
	Duration   string         `json:"duration"`
	Tags       map[string]any `json:"tags"`
}

type FFMpegStream struct {
	Codec_type string `json:"codec_type"`
	Width      int    `json:"width"`
	Height     int    `json:"height"`
}

type FFMpegMetadata struct {
	Streams []FFMpegStream `json:"streams"`
	Format  FFMpegFormat   `json:"format"`
}

func ffmpeg_metadata_cmd(path string) *exec.Cmd {
	return exec.Command(
		"ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", path,
	)
}

func ffmpeg_metadata(path string, wg *sync.WaitGroup) (ans FFMpegMetadata, err error) {
	defer wg.Done()
	cmd := ffmpeg_metadata_cmd(path)
	cmd.Stdin = nil
	cmd.SysProcAttr = &unix.SysProcAttr{Setsid: true}
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	if err = cmd.Run(); err != nil {
		return ans, fmt.Errorf("failed to use ffprobe to read metadata from %s with error: %w and stderr: %s", path, err, stderr.String())
	}
	if err = json.Unmarshal(stdout.Bytes(), &ans); err != nil {
		return ans, fmt.Errorf("could not decode JSON response from ffprobe for %s: %w", path, err)
	}
	return
}

func (c ffmpeg_renderer) Render(path string) (m map[string][]byte, mi metadata, img *images.ImageData, err error) {
	wg := sync.WaitGroup{}
	tempfile, err := os.CreateTemp(magick.TempDirInRAMIfPossible(), "kitty-choose-files-*.webp")
	if err != nil {
		return nil, mi, nil, err
	}
	defer func() { _ = os.Remove(tempfile.Name()); tempfile.Close() }()
	var metadata FFMpegMetadata
	var metadata_error error
	wg.Add(1)
	go func() { metadata, metadata_error = ffmpeg_metadata(path, &wg) }()
	wg.Add(1)
	go func() { img, err = ffmpeg_thumbnail(path, tempfile.Name(), &wg) }()
	wg.Wait()
	if metadata_error != nil {
		return nil, mi, nil, metadata_error
	}
	var ip ImagePreviewRenderer
	if m, mi, img, err = ip.Render(tempfile.Name()); err != nil {
		return
	}
	mi.custom = &metadata

	return
}

func (c ffmpeg_renderer) Unmarshall(m map[string]string) (any, error) {
	data, err := os.ReadFile(m[FFMPEG_METADATA_KEY])
	if err != nil {
		return nil, err
	}
	var ans FFMpegMetadata
	if err = json.Unmarshal(data, &ans); err != nil {
		return nil, err
	}
	return &ans, nil
}

func (c ffmpeg_renderer) ShowMetadata(h *Handler, s ShowData) (offset int) {
	w := func(text string, center bool) {
		if s.height > offset {
			offset += h.render_wrapped_text_in_region(text, s.x, s.y+offset, s.width, s.height-offset, center)
		}
	}
	ext := filepath.Ext(s.abspath)
	text := fmt.Sprintf("%s: %s", ext, humanize.Bytes(uint64(s.metadata.Size())))
	r := s.custom_metadata.custom.(*FFMpegMetadata)
	icon := icons.IconForPath(s.abspath)
	var width, height int
	for _, s := range r.Streams {
		if s.Width > 0 && s.Height > 0 {
			width, height = s.Width, s.Height
			break
		}
	}
	text += fmt.Sprintf(" %dx%d", width, height)
	w(icon+"  "+text, true)
	st := humanize.Time(s.metadata.ModTime())
	if d, perr := strconv.ParseFloat(r.Format.Duration, 64); perr == nil {
		duration := time.Duration(d * float64(time.Second))
		st += fmt.Sprintf(", %s", durafmt.Parse(duration).LimitFirstN(1).String())
	}
	w(st, true)
	return
}

func (c ffmpeg_renderer) String() string {
	return "FFMpeg"
}

func NewFFMpegPreview(
	abspath string, metadata fs.FileInfo, opts Settings, WakeupMainThread func() bool,
) Preview {
	c := ffmpeg_renderer(0)
	if ans, err := NewImagePreview(abspath, metadata, opts, WakeupMainThread, c); err == nil {
		return ans
	} else {
		return NewErrorPreview(err)
	}
}
