package model

import (
	"encoding/json"
	"fmt"
	"github.com/redis/go-redis/v9"
	"hash/fnv"
	"regexp"
	"server/config"
	"server/plugin/db"
	"strconv"
	"strings"
	"time"
)

// Movie 影片基本信息
type Movie struct {
	Id       int64  `json:"id"`       // 影片ID
	Name     string `json:"name"`     // 影片名
	Cid      int64  `json:"cid"`      // 所属分类ID
	CName    string `json:"CName"`    // 所属分类名称
	EnName   string `json:"enName"`   // 英文片名
	Time     string `json:"time"`     // 更新时间
	Remarks  string `json:"remarks"`  // 备注 | 清晰度
	PlayFrom string `json:"playFrom"` // 播放来源
}

// MovieDescriptor 影片详情介绍信息
type MovieDescriptor struct {
	SubTitle    string `json:"subTitle"`    //子标题
	CName       string `json:"cName"`       //分类名称
	EnName      string `json:"enName"`      //英文名
	Initial     string `json:"initial"`     //首字母
	ClassTag    string `json:"classTag"`    //分类标签
	Actor       string `json:"actor"`       //主演
	Director    string `json:"director"`    //导演
	Writer      string `json:"writer"`      //作者
	Blurb       string `json:"blurb"`       //简介, 残缺,不建议使用
	Remarks     string `json:"remarks"`     // 更新情况
	ReleaseDate string `json:"releaseDate"` //上映时间
	Area        string `json:"area"`        // 地区
	Language    string `json:"language"`    //语言
	Year        string `json:"year"`        //年份
	State       string `json:"state"`       //影片状态 正片|预告...
	UpdateTime  string `json:"updateTime"`  //更新时间
	AddTime     int64  `json:"addTime"`     //资源添加时间戳
	DbId        int64  `json:"dbId"`        //豆瓣id
	DbScore     string `json:"dbScore"`     // 豆瓣评分
	Hits        int64  `json:"hits"`        //影片热度
	Content     string `json:"content"`     //内容简介
}

// MovieBasicInfo 影片基本信息
type MovieBasicInfo struct {
	Id       int64  `json:"id"`       //影片Id
	Cid      int64  `json:"cid"`      //分类ID
	Pid      int64  `json:"pid"`      //一级分类ID
	Name     string `json:"name"`     //片名
	SubTitle string `json:"subTitle"` //子标题
	CName    string `json:"cName"`    //分类名称
	State    string `json:"state"`    //影片状态 正片|预告...
	Picture  string `json:"picture"`  //简介图片
	Actor    string `json:"actor"`    //主演
	Director string `json:"director"` //导演
	Blurb    string `json:"blurb"`    //简介, 不完整
	Remarks  string `json:"remarks"`  // 更新情况
	Area     string `json:"area"`     // 地区
	Year     string `json:"year"`     //年份
}

// MovieUrlInfo 影视资源url信息
type MovieUrlInfo struct {
	Episode string `json:"episode"` // 集数
	Link    string `json:"link"`    // 播放地址
}

// MovieDetail 影片详情信息
type MovieDetail struct {
	Id       int64    `json:"id"`       //影片Id
	Cid      int64    `json:"cid"`      //分类ID
	Pid      int64    `json:"pid"`      //一级分类ID
	Name     string   `json:"name"`     //片名
	Picture  string   `json:"picture"`  //简介图片
	PlayFrom []string `json:"playFrom"` // 播放来源
	DownFrom string   `json:"DownFrom"` //下载来源 例: http
	//PlaySeparator   string              `json:"playSeparator"` // 播放信息分隔符
	PlayList        [][]MovieUrlInfo    `json:"playList"`     //播放地址url
	DownloadList    [][]MovieUrlInfo    `json:"downloadList"` // 下载url地址
	MovieDescriptor `json:"descriptor"` //影片描述信息
}

// SaveMoves  保存影片分页请求list
func SaveMoves(list []Movie) (err error) {
	// 整合数据
	for _, m := range list {
		//score, _ := time.ParseInLocation(time.DateTime, m.Time, time.Local)
		movie, _ := json.Marshal(m)
		// 以Cid为目录为集合进行存储, 便于后续搜索, 以影片id为分值进行存储 例 MovieList:Cid%d
		err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf(config.MovieListInfoKey, m.Cid), redis.Z{Score: float64(m.Id), Member: movie}).Err()
	}
	return err
}

// AllMovieInfoKey 获取redis中所有的影视列表信息key MovieList:Cid
func AllMovieInfoKey() []string {
	return db.Rdb.Keys(db.Cxt, fmt.Sprint("MovieList:Cid*")).Val()
}

// GetMovieListByKey 获取指定分类的影片列表数据
func GetMovieListByKey(key string) []string {
	return db.Rdb.ZRange(db.Cxt, key, 0, -1).Val()
}

// SaveDetails 保存影片详情信息到redis中 格式: MovieDetail:Cid?:Id?
func SaveDetails(list []MovieDetail) (err error) {
	// 遍历list中的信息
	for _, detail := range list {
		// 序列化影片详情信息
		data, _ := json.Marshal(detail)
		// 1. 原使用Zset存储, 但是不便于单个检索 db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Cid%d", config.MovieDetailKey, detail.Cid), redis.Z{Score: float64(detail.Id), Member: member}).Err()
		// 改为普通 k v 存储, k-> id关键字, v json序列化的结果
		err = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieDetailKey, detail.Cid, detail.Id), data, config.CategoryTreeExpired).Err()
		// 2. 同步保存简略信息到redis中
		SaveMovieBasicInfo(detail)
		// 3. 保存Search检索信息到redis, 暂时搁置
		//if err == nil {
		//	// 转换 detail信息
		//	searchInfo := ConvertSearchInfo(detail)
		//	// 放弃redis进行检索, 多条件处理不方便
		//	//err = AddSearchInfo(searchInfo)
		//	// 只存储用于检索对应影片的关键字信息
		//	SearchKeyword(searchInfo)
		//}

	}
	// 保存一份search信息到mysql, 批量存储
	BatchSaveSearchInfo(list)
	return err
}

// SaveMovieBasicInfo 摘取影片的详情部分信息转存为影视基本信息
func SaveMovieBasicInfo(detail MovieDetail) {
	basicInfo := MovieBasicInfo{
		Id:       detail.Id,
		Cid:      detail.Cid,
		Pid:      detail.Pid,
		Name:     detail.Name,
		SubTitle: detail.SubTitle,
		CName:    detail.CName,
		State:    detail.State,
		Picture:  detail.Picture,
		Actor:    detail.Actor,
		Director: detail.Director,
		Blurb:    detail.Blurb,
		Remarks:  detail.Remarks,
		Area:     detail.Area,
		Year:     detail.Year,
	}
	data, _ := json.Marshal(basicInfo)
	_ = db.Rdb.Set(db.Cxt, fmt.Sprintf(config.MovieBasicInfoKey, detail.Cid, detail.Id), data, config.CategoryTreeExpired).Err()
}

// SaveSitePlayList 仅保存播放url列表信息到当前站点
func SaveSitePlayList(siteName string, list []MovieDetail) (err error) {
	// 如果list 为空则直接返回
	if len(list) <= 0 {
		return nil
	}
	res := make(map[string]string)
	for _, d := range list {
		if len(d.PlayList) > 0 {
			data, _ := json.Marshal(d.PlayList[0])
			// 不保存电影解说类
			if strings.Contains(d.CName, "解说") {
				continue
			}
			// 如果DbId不为0, 则以dbID作为key进行hash额外存储一次
			if d.DbId > 0 {
				res[GenerateHashKey(d.DbId)] = string(data)
			}
			res[GenerateHashKey(d.Name)] = string(data)
		}
	}
	// 如果结果不为空,则将数据保存到redis中
	if len(res) > 0 {
		// 保存形式 key: MultipleSource:siteName Hash[hash(movieName)]list
		err = db.Rdb.HMSet(db.Cxt, fmt.Sprintf(config.MultipleSiteDetail, siteName), res).Err()
	}
	return
}

// AddSearchInfo 将影片关键字信息整合后存入search 集合中
func AddSearchInfo(searchInfo SearchInfo) (err error) {
	// 片名 Name 分类 CName 类别标签 classTag 地区 Area 语言 Language 年份 Year 首字母 Initial, 排序
	data, _ := json.Marshal(searchInfo)
	// 时间排序 score -->时间戳 DbId 排序 --> 热度, 评分排序 DbScore
	err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Pid%d", config.SearchTimeListKey, searchInfo.Pid), redis.Z{Score: float64(searchInfo.UpdateStamp), Member: data}).Err()
	err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Pid%d", config.SearchScoreListKey, searchInfo.Pid), redis.Z{Score: searchInfo.Score, Member: data}).Err()
	err = db.Rdb.ZAdd(db.Cxt, fmt.Sprintf("%s:Pid%d", config.SearchHeatListKey, searchInfo.Pid), redis.Z{Score: float64(searchInfo.Hits), Member: data}).Err()
	// 添加搜索关键字信息
	SearchKeyword(searchInfo)
	return
}

// SearchKeyword 设置search关键字集合
func SearchKeyword(search SearchInfo) {
	// 首先获取redis中的search 关键字信息
	key := fmt.Sprintf("%s:Pid%d", config.SearchKeys, search.Pid)
	keyword := db.Rdb.HGetAll(db.Cxt, key).Val()
	if keyword["Year"] == "" {
		currentYear := time.Now().Year()
		year := ""
		for i := 0; i < 12; i++ {
			// 提供当前年份前推十二年的搜索
			year = fmt.Sprintf("%s,%d", year, currentYear-i)
		}
		initial := ""
		for i := 65; i <= 90; i++ {
			initial = fmt.Sprintf("%s,%c", initial, i)
		}
		keyword = map[string]string{
			//"Name":     "",
			"Category": "",
			"Tag":      "",
			"Area":     "",
			"Language": "",
			"Year":     strings.Trim(year, ","),
			"Initial":  strings.Trim(initial, ","),
			"Sort":     "Time,Db,Score", // 默认,一般不修改
		}
	}
	// 分类标签处理
	if !strings.Contains(keyword["Category"], search.CName) {
		keyword["Category"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Category"], search.CName), ",")
	}
	// 影视内容分类处理
	if strings.Contains(search.ClassTag, "/") {
		for _, t := range strings.Split(search.ClassTag, "/") {
			if !strings.Contains(keyword["Tag"], t) {
				keyword["Tag"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Tag"], t), ",")
			}
		}
	} else if strings.Contains(search.ClassTag, ",") {
		for _, t := range strings.Split(search.ClassTag, ",") {
			if !strings.Contains(keyword["Tag"], t) {
				keyword["Tag"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Tag"], t), ",")
			}
		}
	} else {
		if !strings.Contains(keyword["Tag"], search.ClassTag) {
			keyword["Tag"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Tag"], search.ClassTag), ",")
		}
	}
	// 如果地区中包含 / 分隔符 则先进行切分处理
	if strings.Contains(search.Area, "/") {
		for _, s := range strings.Split(search.Area, "/") {
			if !strings.Contains(keyword["Area"], strings.TrimSpace(s)) {
				keyword["Area"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Area"], s), ",")
			}
		}
	} else if strings.Contains(search.Area, ",") {
		for _, s := range strings.Split(search.Area, ",") {
			if !strings.Contains(keyword["Area"], strings.TrimSpace(s)) {
				keyword["Area"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Area"], s), ",")
			}
		}
	} else {
		if !strings.Contains(keyword["Area"], search.Area) {
			keyword["Area"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Area"], search.Area), ",")
		}
	}
	// 语言处理
	if strings.Contains(search.Language, "/") {
		for _, l := range strings.Split(search.Language, "/") {
			if !strings.Contains(keyword["Language"], l) {
				keyword["Language"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Language"], l), ",")
			}
		}

	} else if strings.Contains(search.Language, ",") {
		for _, l := range strings.Split(search.Language, ",") {
			if !strings.Contains(keyword["Language"], l) {
				keyword["Language"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Language"], l), ",")
			}
		}
	} else {
		if !strings.Contains(keyword["Language"], search.Language) {
			keyword["Language"] = strings.Trim(fmt.Sprintf("%s,%s", keyword["Language"], search.Language), ",")
		}
	}
	_ = db.Rdb.HMSet(db.Cxt, key, keyword).Err()
}

// BatchSaveSearchInfo 批量保存Search信息
func BatchSaveSearchInfo(list []MovieDetail) {
	var infoList []SearchInfo
	for _, v := range list {
		infoList = append(infoList, ConvertSearchInfo(v))
	}
	// 将检索信息存入redis中做一次转存
	RdbSaveSearchInfo(infoList)

	// 废弃方案, 频繁大量入库容易引起主键冲突, 事务影响速率
	// 批量插入时应对已存在数据进行检测, 使用mysql事务进行锁表
	//BatchSave(infoList)
	// 使用批量添加or更新
	//BatchSaveOrUpdate(infoList)
}

// ConvertSearchInfo 将detail信息处理成 searchInfo
func ConvertSearchInfo(detail MovieDetail) SearchInfo {
	score, _ := strconv.ParseFloat(detail.DbScore, 64)
	stamp, _ := time.ParseInLocation(time.DateTime, detail.UpdateTime, time.Local)
	// detail中的年份信息并不准确, 因此采用 ReleaseDate中的年份
	year, err := strconv.ParseInt(regexp.MustCompile(`[1-9][0-9]{3}`).FindString(detail.ReleaseDate), 10, 64)
	if err != nil {
		year = 0
	}
	return SearchInfo{
		Mid:         detail.Id,
		Cid:         detail.Cid,
		Pid:         detail.Pid,
		Name:        detail.Name,
		SubTitle:    detail.SubTitle,
		CName:       detail.CName,
		ClassTag:    detail.ClassTag,
		Area:        detail.Area,
		Language:    detail.Language,
		Year:        year,
		Initial:     detail.Initial,
		Score:       score,
		Hits:        detail.Hits,
		UpdateStamp: stamp.Unix(),
		State:       detail.State,
		Remarks:     detail.Remarks,
		// releaseDate 部分影片缺失该参数, 所以使用添加时间作为上映时间排序
		ReleaseDate: detail.AddTime,
	}
}

// GetBasicInfoByKey 获取Id对应的影片基本信息
func GetBasicInfoByKey(key string) MovieBasicInfo {
	// 反序列化得到的结果
	data := []byte(db.Rdb.Get(db.Cxt, key).Val())
	basic := MovieBasicInfo{}
	_ = json.Unmarshal(data, &basic)
	return basic
}

// GetDetailByKey 获取影片对应的详情信息
func GetDetailByKey(key string) MovieDetail {
	// 反序列化得到的结果
	data := []byte(db.Rdb.Get(db.Cxt, key).Val())
	detail := MovieDetail{}
	_ = json.Unmarshal(data, &detail)
	return detail
}

/*
	对附属播放源入库时的name|dbID进行处理,保证唯一性
1. 去除name中的所有空格
2. 去除name中含有的别名～.*～
3. 去除name首尾的标点符号
4. 将处理完成后的name转化为hash值作为存储时的key
*/
// GenerateHashKey 存储播放源信息时对影片名称进行处理, 提高各站点间同一影片的匹配度
func GenerateHashKey[K string | ~int | int64](key K) string {
	mName := fmt.Sprint(key)
	//1. 去除name中的所有空格
	mName = regexp.MustCompile(`\s`).ReplaceAllString(mName, "")
	//2. 去除name中含有的别名～.*～
	mName = regexp.MustCompile(`～.*～$`).ReplaceAllString(mName, "")
	//3. 去除name首尾的标点符号
	mName = regexp.MustCompile(`^[[:punct:]]+|[[:punct:]]+$`).ReplaceAllString(mName, "")
	// 部分站点包含 动画版, 特殊别名 等字符, 需进行删除
	//mName = regexp.MustCompile(`动画版`).ReplaceAllString(mName, "")
	mName = regexp.MustCompile(`季.*`).ReplaceAllString(mName, "季")
	//4. 将处理完成后的name转化为hash值作为存储时的key
	h := fnv.New32a()
	_, err := h.Write([]byte(mName))
	if err != nil {
		return ""
	}
	return fmt.Sprint(h.Sum32())
}
