package packer

import (
	"bytes"
	"fmt"
	"github.com/Eyevinn/mp4ff/mp4"
	"github.com/bluenviron/gortsplib/v4/pkg/format"
	"log"
	"os"
	"os/exec"
	"strings"
)

/*
	// Горутина для объединения видео и аудио в контейнер MP4
	go func(oldH264Filename, oldPCMFilename, oldMP4Filename string) {
		// Создание MP4 файл
		//if err := createMP4File(oldH264Filename, aacFilename, oldMP4Filename, h264Params); err != nil {
		//	log.Printf("Ошибка создания MP4: %v", err)
		//} else {
		//	log.Printf("Файлы %s, %s успешно объединены в контейнер MP4: %s\n",
		//		oldH264Filename, aacFilename, oldMP4Filename)
		//}

		// Часть программы, вызывающая ffmpeg для объединения видеофайла .h264 аудиофайла .pcm в контейнер .mp4 -
		// остается в программе до реализации аналогичного функционала без использования ffmpeg.
		// Передаем значения переменных в замыкание, чтобы избежать их изменения
		muxedFilename := strings.Replace(oldH264Filename, ".h264", ".mp4", 1)
		cmd := exec.Command("ffmpeg",
			"-y",
			"-fflags", "+genpts",
			"-r", "25",
			"-i", oldH264Filename,
			"-i", aacFilename,
			"-c:v", "copy",
			muxedFilename,
		)

		if err := cmd.Run(); err != nil {
			log.Printf("Ошибка при объединении фрагментов: %v", err)
		} else {
			log.Printf("Фрагменты объединены в файл %s", muxedFilename)
		}

		// Удаление временных файлов
		//os.Remove(oldH264Filename)
		//os.Remove(aacFilename)
		//os.Remove(oldPCMFilename)

		log.Printf("Файлы %s, %s, %s успешно удалены\n", oldH264Filename, aacFilename, oldPCMFilename)
	}(oldH264Filename, oldPCMFilename, oldMP4Filename)
*/

// createMP4FileFfmpeg takes H264 and AAC files to make MP4 file using ffmpeg.
func createMP4FileFfmpeg(H264Filename, aacFilename string, newTimestamp string) error {
	muxedFilename := strings.Replace(H264Filename, ".h264", ".mp4", 1)

	cmd := exec.Command("ffmpeg",
		"-y",
		"-fflags", "+genpts",
		"-r", "25",
		"-i", H264Filename,
		"-i", aacFilename,
		"-c:v", "copy",
		muxedFilename,
	)

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("combining fragments error: %v", err)
	}

	return nil
}

// Sample – единый тип для описания сэмпла (для видео и аудио)
type Sample struct {
	Data   []byte
	Offset uint64
	Size   uint32
	Dur    uint32
	IsSync bool
}

// createMP4File packs H264 and AAC to MP4.
func createMP4File(videoFile, audioFile, outputFile string, formaH264 *format.H264) error {
	videoData, err := os.ReadFile(videoFile)
	if err != nil {
		return err
	}
	videoSamples := parseH264Samples(videoData)

	audioData, err := os.ReadFile(audioFile)
	if err != nil {
		return err
	}
	audioSamples := parseAACSamples(audioData)

	f := mp4.NewFile()

	videoTrack := createVideoTrack(formaH264.SPS, formaH264.PPS)
	audioTrack := createAudioTrack(8000, 1)

	addSamplesToVideoTrack(videoTrack, videoSamples)
	addSamplesToAudioTrack(audioTrack, audioSamples, 8000)

	moov := mp4.NewMoovBox()
	moov.AddChild(videoTrack)
	moov.AddChild(audioTrack)
	f.AddChild(moov, 0)

	mdat := mp4.MdatBox{}
	mdat.Data = append(videoData, audioData...)
	f.AddChild(&mdat, 0)

	outFile, err := os.Create(outputFile)
	if err != nil {
		return err
	}
	defer outFile.Close()
	return f.Encode(outFile)
}

// createVideoTrack создаёт видео-трек на основе SPS и PPS.
func createVideoTrack(sps, pps []byte) *mp4.TrakBox {
	sps = stripAnnexB(sps)
	pps = stripAnnexB(pps)

	if len(sps) < 4 {
		log.Fatalln("SPS слишком короткий или пустой")
	}
	if len(pps) < 1 {
		log.Fatalln("PPS слишком короткий или пустой")
	}

	trak := mp4.NewTrakBox()
	timescale := uint32(90000)

	mdhd := &mp4.MdhdBox{
		Timescale: timescale,
		Language:  convertLanguage("und"),
	}

	hdlr := &mp4.HdlrBox{
		HandlerType: "vide",
		Name:        "VideoHandler",
	}

	avc1 := mp4.NewVisualSampleEntryBox("avc1")
	avc1.Width = 1280
	avc1.Height = 720

	avcC, err := mp4.CreateAvcC([][]byte{sps}, [][]byte{pps}, true)
	if err != nil {
		log.Fatalf("ошибка создания avcC: %v", err)
	}
	avcC.AVCLevelIndication = 0x1f
	avc1.AddChild(avcC)

	stbl := mp4.NewStblBox()
	stsd := mp4.NewStsdBox()
	stsd.AddChild(avc1)
	stbl.AddChild(stsd)
	stbl.AddChild(&mp4.SttsBox{})
	stbl.AddChild(&mp4.StscBox{})
	stbl.AddChild(&mp4.StszBox{})
	stbl.AddChild(&mp4.StcoBox{})

	trak.Mdia = &mp4.MdiaBox{
		Mdhd: mdhd,
		Hdlr: hdlr,
		Minf: &mp4.MinfBox{
			Stbl: stbl,
		},
	}

	return trak
}

// stripAnnexB удаляет стартовый код Annex-B из NAL-единицы.
func stripAnnexB(nalu []byte) []byte {
	if len(nalu) >= 4 && bytes.Equal(nalu[:4], []byte{0x00, 0x00, 0x00, 0x01}) {
		return nalu[4:]
	}
	if len(nalu) >= 3 && bytes.Equal(nalu[:3], []byte{0x00, 0x00, 0x01}) {
		return nalu[3:]
	}
	return nalu
}

// createAudioTrack создаёт аудио-трек.
func createAudioTrack(sampleRate, channels int) *mp4.TrakBox {
	trak := mp4.NewTrakBox()
	timescale := uint32(sampleRate)

	mdhd := &mp4.MdhdBox{
		Timescale: timescale,
		Language:  convertLanguage("und"),
	}

	hdlr := &mp4.HdlrBox{
		HandlerType: "soun",
		Name:        "SoundHandler",
	}

	mp4a := mp4.NewAudioSampleEntryBox("mp4a")
	mp4a.ChannelCount = uint16(channels)
	mp4a.SampleRate = uint16(sampleRate)
	mp4a.SampleSize = 16

	asc := getAACConfig(sampleRate, channels)
	esds := createESDSBox(asc)
	mp4a.AddChild(esds)

	stbl := mp4.NewStblBox()
	stsd := mp4.NewStsdBox()
	stsd.AddChild(mp4a)
	stbl.AddChild(stsd)
	stbl.AddChild(&mp4.SttsBox{})
	stbl.AddChild(&mp4.StscBox{})
	stbl.AddChild(&mp4.StszBox{})
	stbl.AddChild(&mp4.StcoBox{})

	trak.Mdia = &mp4.MdiaBox{
		Mdhd: mdhd,
		Hdlr: hdlr,
		Minf: &mp4.MinfBox{
			Stbl: stbl,
		},
	}

	return trak
}

// createESDSBox создаёт ESDS-бокс для AAC.
func createESDSBox(asc []byte) *mp4.EsdsBox {
	return &mp4.EsdsBox{
		ESDescriptor: mp4.ESDescriptor{
			DecConfigDescriptor: &mp4.DecoderConfigDescriptor{
				ObjectType: 0x40,
				StreamType: 0x05,
				DecSpecificInfo: &mp4.DecSpecificInfoDescriptor{
					DecConfig: asc,
				},
			},
		},
	}
}

func getAACConfig(sampleRate, channels int) []byte {
	// Пример конфигурации для 8000 Гц, моно
	return []byte{0x12, 0x10}
}

// addSamplesToVideoTrack заполняет таблицы сэмплов видео-трека.
func addSamplesToVideoTrack(trak *mp4.TrakBox, samples []Sample) {
	stbl := trak.Mdia.Minf.Stbl

	stts := mp4.SttsBox{}
	for _, s := range samples {
		stts.SampleCount = []uint32{1}
		stts.SampleTimeDelta = []uint32{s.Dur}
	}

	stsz := mp4.StszBox{}
	for _, s := range samples {
		stsz.SampleSize = []uint32{s.Size}
	}

	stss := mp4.StssBox{}
	for i, s := range samples {
		if s.IsSync {
			stss.SampleNumber = []uint32{uint32(i + 1)}
		}
	}

	stbl.AddChild(&stts)
	stbl.AddChild(&stsz)
	stbl.AddChild(&stss)
}

// addSamplesToAudioTrack заполняет таблицы сэмплов аудио-трека.
func addSamplesToAudioTrack(trak *mp4.TrakBox, samples []Sample, sampleRate int) {
	stbl := trak.Mdia.Minf.Stbl

	stts := mp4.SttsBox{}
	for _, s := range samples {
		stts.SampleCount = []uint32{1}
		stts.SampleTimeDelta = []uint32{s.Dur}
	}

	stsz := mp4.StszBox{}
	for _, s := range samples {
		stsz.SampleSize = []uint32{uint32(len(s.Data))}
	}

	stbl.AddChild(&stts)
	stbl.AddChild(&stsz)
}

// parseH264Samples парсит H.264 поток на сэмплы.
func parseH264Samples(data []byte) []Sample {
	var samples []Sample
	start := 0
	for i := 0; i < len(data)-3; i++ {
		if bytes.Equal(data[i:i+4], []byte{0x00, 0x00, 0x00, 0x01}) {
			if start != 0 {
				sampleData := data[start:i]
				samples = append(samples, Sample{
					Size:   uint32(len(sampleData)),
					Dur:    3600,
					IsSync: isKeyFrame(sampleData),
				})
			}
			start = i + 4
		}
	}
	if start < len(data) {
		sampleData := data[start:]
		samples = append(samples, Sample{
			Size:   uint32(len(sampleData)),
			Dur:    3600,
			IsSync: isKeyFrame(sampleData),
		})
	}
	return samples
}

// isKeyFrame определяет, является ли NALU ключевым (IDR).
func isKeyFrame(nalu []byte) bool {
	if len(nalu) == 0 {
		return false
	}
	nalType := nalu[0] & 0x1F
	return nalType == 5
}

// parseAACSamples парсит AAC данные и возвращает сэмплы.
func parseAACSamples(data []byte) []Sample {
	var samples []Sample
	frameSize := 1024 // примерный размер AAC-фрейма
	for i := 0; i < len(data); i += frameSize {
		end := i + frameSize
		if end > len(data) {
			end = len(data)
		}
		samples = append(samples, Sample{
			Data: data[i:end],
			Dur:  1024,
			Size: uint32(end - i),
		})
	}
	return samples
}

// convertLanguage преобразует строку языка в 16-битное значение.
func convertLanguage(lang string) uint16 {
	if len(lang) != 3 {
		return 0x55C4
	}
	b1 := lang[0] - 0x60
	b2 := lang[1] - 0x60
	b3 := lang[2] - 0x60
	return uint16((b1 << 10) | (b2 << 5) | b3)
}
