package fllcc

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"gogs.daxia.dev/huanan/pkg.daxia.dev/fllcc/cache"

	"github.com/beevik/guid"
	"github.com/grafov/m3u8"
)

const (
	TYPE_VIDEO = "video"
	TYPE_KEY   = "key"
	TYPE_TS    = "ts"
	VERSION    = "0.1"
)

var port = 4000
var host = "127.0.0.1"
var playUrlMap = map[string]string{}
var playUrlMapLock = sync.Mutex{}

var activeUrlMap = map[string]int64{}
var activeMasterUrlMap = map[string]string{}
var activeUrlMapLock = sync.Mutex{}
var envMode = "prod"
var srv *http.Server = nil
var srvLock = sync.Mutex{}

var fileCache = &cache.FileCache{}

var client = http.Client{
	Timeout: 15 * time.Second,
}

var clientShortTimeout = http.Client{
	Timeout: 3 * time.Second,
}

//启动内部服务器
func startup(dir string, cacheDir string, size int) {
	if cacheDir == "" || cacheDir == "/" {
		outputLog("启动失败")
		return
	}

	http.HandleFunc("/video/", videoHandler)
	http.HandleFunc("/key/", keyHandler)
	http.HandleFunc("/ts/", tsHandler)
	http.HandleFunc("/ping", pingHandler)
	http.HandleFunc("/version", versionHandler)

	fileCache = cache.NewFileCache(cacheDir, size)

	//这里有权限控制，没事
	_ = fileCache.ClearCache()

	if dir != "" {
		http.Handle("/v/",
			http.StripPrefix("/v/", http.FileServer(http.Dir(dir))))

		//打印一下目录内容
		//获取文件或目录相关信息
		fileInfoList, err := ioutil.ReadDir(dir)
		if err != nil {
			log.Println(err)
		} else {
			fmt.Println(len(fileInfoList))
			for i := range fileInfoList {
				log.Println(fileInfoList[i].Name()) //打印当前文件或目录下的文件或目录名
			}
		}
	}

	log.Println("run mode:" + envMode)

	//选取4000 - 5000的端口
	freePort, err := getFreePort()
	if err != nil {
		outputLog(err)
		return
	}

	port = freePort

	startThread()

	d := time.Second * 5

	t := time.NewTicker(d)
	defer t.Stop()

	for {
		<-t.C

		//检测一下是否真的停止了
		pingUrl := fmt.Sprintf("http://%s:%d/ping", host, port)
		resp, err := client.Get(pingUrl)
		if err == nil {
			resp.Body.Close()
			continue
		}

		outputLog("重启服务器")
		startThread()
	}
}

func serverLoop(srv *http.Server) {
	defer func() {
		if p := recover(); p != nil {
			outputLog("pinic:", p)
		}
	}()

	srvLock.Lock()
	srvTmp := &http.Server{Addr: fmt.Sprintf("%s:%d", host, port)}
	srv = srvTmp
	srvLock.Unlock()

	err := srvTmp.ListenAndServe()
	if err != nil {
		outputLog(err)
	}
}

func startThread() {
	srvLock.Lock()
	defer srvLock.Unlock()

	if srv != nil {
		//出错的时候才停止
		_ = srv.Shutdown(context.Background())
	}

	//启动服务器，但重启的时候，会退出
	go serverLoop(srv)
}

func getPlayUrl(oriPlayUrl string) string {
	if strings.HasPrefix(oriPlayUrl, "file://") {
		oriPlayUrl = strings.TrimPrefix(oriPlayUrl, "file://")
		oriPlayUrl = fmt.Sprintf("http://%s:%d/v/%s", host, port, oriPlayUrl)
	}

	playUrlMapLock.Lock()
	defer playUrlMapLock.Unlock()

	randUrl := getRandomUrl(TYPE_VIDEO)
	playUrlMap[randUrl] = oriPlayUrl

	activeUrlMapLock.Lock()
	defer activeUrlMapLock.Unlock()

	md5 := getMD5Hash(oriPlayUrl)
	activeUrlMap[md5] = time.Now().Unix()

	return randUrl
}

//处理ts，有缓存使用缓存，没有就获取数据，写入缓存，返回
func tsHandler(w http.ResponseWriter, r *http.Request) {
	defer func() {
		_ = recover()
	}()

	curPath := r.URL.String()

	//提取ts的key
	pathList := strings.Split(curPath, "/")

	key := pathList[len(pathList)-1]

	data, err := fileCache.GetData(key)
	if err == nil {
		_, _ = w.Write(data)
		return
	}

	oriPath := ""

	//获取原路径
	func() {
		playUrlMapLock.Lock()
		defer playUrlMapLock.Unlock()

		oriPath = playUrlMap[curPath]
	}()

	if oriPath == "" {
		outputLog("获取原路径失败：" + curPath)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	resp, err := client.Get(oriPath)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	defer resp.Body.Close()

	bodyBuf := make([]byte, 0)
	const bufLen = 4096
	for {
		buf := make([]byte, bufLen)
		n, err := resp.Body.Read(buf)
		if n > 0 {
			//调整缓冲去大小
			buf = buf[:n]
			bodyBuf = append(bodyBuf, buf...)

			_, _ = w.Write(buf)
			continue
		}

		if err == io.EOF {
			break
		}

		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}
	_ = fileCache.SetData(key, bodyBuf)
}

//获取原来的key，解密内容后，返回正常的内容
func keyHandler(w http.ResponseWriter, r *http.Request) {
	curPath := r.URL.String()
	oriPath := ""

	//校验key的有效性
	pathList := strings.Split(curPath, "_")
	if len(pathList) != 2 {
		outputLog("路径格式不对")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	token := r.Header.Get("token")
	if token != "C203561DD73AEDC699358DFA92217E57" && envMode != "dev" {
		outputLog("token不对")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	err := func() error {
		activeUrlMapLock.Lock()
		defer activeUrlMapLock.Unlock()

		md5Url := pathList[1]
		md5Map := activeMasterUrlMap[md5Url]
		if md5Map != "" {
			md5Url = md5Map
		}

		//urlActiveTime := activeUrlMap[md5Url]

		//超过5秒，没有心跳，停止播放
		//if time.Now().Unix()-urlActiveTime > 5 && envMode != "dev" {
		//	return errors.New("心跳错误")
		//}

		return nil
	}()

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	func() {
		playUrlMapLock.Lock()
		defer playUrlMapLock.Unlock()

		oriPath = playUrlMap[curPath]
	}()

	if oriPath == "" {
		outputLog("获取原路径失败：" + curPath)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	resp, err := client.Get(oriPath)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	bodyStr := string(body)

	//解密key数据
	bodyStr = strings.TrimSpace(bodyStr)

	outputLog("key数据：" + bodyStr)

	//判断是不是试播地址，尝试解密
	tryKey := func() string {
		data, err := base64.StdEncoding.DecodeString(bodyStr)
		dataStr := string(data)

		outputLog("解码的key：" + dataStr)

		suffix := "123456"
		if err == nil && strings.HasSuffix(dataStr, suffix) {
			//这个就是试播
			dataStr = strings.TrimSuffix(dataStr, suffix)

			outputLog("试播key：" + dataStr)
			//_, _ = w.Write([]byte(dataStr))
			return dataStr
		}

		outputLog("不是试播key：" + dataStr)
		return ""
	}()

	outputLog("try key:" + tryKey)
	if tryKey != "" {
		outputLog("返回key" + tryKey)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(tryKey))
		return
	}

	outputLog("进入非试播路径")
	descData, err := descData(bodyStr, "24a1d6ff39d8")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	data, err := base64.StdEncoding.DecodeString(descData)
	if err != nil {
		outputLog(err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	_, _ = w.Write(data)
}

func videoHandler(w http.ResponseWriter, r *http.Request) {
	path := r.URL.String()
	pathList := strings.Split(path, "/")

	id := pathList[len(pathList)-1]

	//获取完整请求地址
	randPath := getUrlByID(TYPE_VIDEO, id)
	oriPath := ""

	err := func() error {
		playUrlMapLock.Lock()
		defer playUrlMapLock.Unlock()

		oriPath = playUrlMap[randPath]
		if oriPath == "" {
			return errors.New("not exists")
		}

		return nil
	}()

	if err != nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	resp, err := client.Get(oriPath)
	if err != nil {
		outputLog("get err:" + err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	bodyStr := string(body)
	newM3U8Str, err := replaceUrl(oriPath, bodyStr)
	if err != nil {
		outputLog("解析内容失败：" + bodyStr)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	_, _ = w.Write([]byte(newM3U8Str))
}

//ping 测试
func pingHandler(w http.ResponseWriter, r *http.Request) {
	_, _ = w.Write([]byte("pong"))
}

//version 版本
func versionHandler(w http.ResponseWriter, r *http.Request) {
	_, _ = w.Write([]byte(VERSION))
}

func getRandomUrl(typeName string) string {
	guid := guid.New().String()
	randUrl := fmt.Sprintf("http://%s:%d/%s/%s", host, port, typeName, guid)
	if typeName == TYPE_VIDEO {
		randUrl += ".m3u8"
	}

	return randUrl
}

func replaceUrl(baseUrl string, m3u8Str string) (string, error) {
	playUrlMapLock.Lock()
	defer playUrlMapLock.Unlock()

	urlMd5 := getMD5Hash(baseUrl)

	//解析m3u8内容，替换一下地址，加密key，用内部的，ts文件，用绝对地址
	p, listType, err := m3u8.DecodeFrom(strings.NewReader(m3u8Str), true)
	if err != nil {
		outputLog(err.Error())
		return "", err
	}

	if listType == m3u8.MASTER {
		masterPlayList := p.(*m3u8.MasterPlaylist)
		for _, item := range masterPlayList.Variants {
			masterUrl := item.URI
			oriUrl, _ := urlToABS(baseUrl, masterUrl)
			randUrl := getRandomUrl(TYPE_VIDEO)
			item.URI = randUrl

			playUrlMap[randUrl] = oriUrl

			func() {
				activeUrlMapLock.Lock()
				defer activeUrlMapLock.Unlock()

				activeMasterUrlMap[getMD5Hash(oriUrl)] = urlMd5
			}()
		}

		return masterPlayList.String(), nil
	}

	if listType != m3u8.MEDIA {
		return "", errors.New("解析失败")
	}

	mediaPlayList := p.(*m3u8.MediaPlaylist)
	if mediaPlayList.Key != nil {
		oriUrl, _ := urlToABS(baseUrl, mediaPlayList.Key.URI)
		randUrl := getRandomUrlRelative(TYPE_KEY) + "_" + urlMd5
		mediaPlayList.Key.URI = fmt.Sprintf("http://%s:%d%s", host, port, randUrl)

		playUrlMap[randUrl] = oriUrl
	}

	for _, item := range mediaPlayList.Segments {
		if item == nil {
			continue
		}

		//ts路径，用全路径
		oriUrl, _ := urlToABS(baseUrl, item.URI)
		randUrl := fmt.Sprintf("/ts/%s", getMD5Hash(oriUrl))
		playUrlMap[randUrl] = oriUrl

		item.URI = fmt.Sprintf("http://%s:%d/%s", host, port, randUrl)

		if item.Key == nil {
			continue
		}

		{
			oriUrl, _ := urlToABS(baseUrl, item.Key.URI)
			randUrl := getRandomUrlRelative(TYPE_KEY) + "_" + urlMd5
			item.Key.URI = fmt.Sprintf("http://%s:%d%s", host, port, randUrl)

			playUrlMap[randUrl] = oriUrl
		}
	}

	return mediaPlayList.String(), nil
}

func getRandomUrlRelative(typeName string) string {
	guid := guid.New().String()
	return fmt.Sprintf("/%s/%s", typeName, guid)
}

func geRandomUrlAbs(typeName string) string {
	guid := guid.New().String()
	return fmt.Sprintf("http://%s:%d/%s/%s", host, port, typeName, guid)
}

func getUrlByID(typeName string, id string) string {
	return fmt.Sprintf("http://%s:%d/%s/%s", host, port, typeName, id)
}
