package service

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/IceWhaleTech/CasaOS/model"
	"github.com/IceWhaleTech/CasaOS/pkg/config"
	"github.com/IceWhaleTech/CasaOS/pkg/quic_helper"
	"github.com/IceWhaleTech/CasaOS/pkg/utils"
	"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
	httper2 "github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
	"github.com/IceWhaleTech/CasaOS/pkg/utils/ip_helper"
	port2 "github.com/IceWhaleTech/CasaOS/pkg/utils/port"
	model2 "github.com/IceWhaleTech/CasaOS/service/model"
	"github.com/IceWhaleTech/CasaOS/types"
	"github.com/lucas-clemente/quic-go"
	"gorm.io/gorm"
)

type PersonService interface {
	GetPersionInfo(token string) (m model.PersionModel, err error)
	GetPersionNetWorkTypeDetection() string
}

type personService struct {
	db *gorm.DB
}

var IpInfo model.PersionModel
var CancelList map[string]string
var InternalInspection map[string][]string

func PushIpInfo(token string) {

	m := model.PersionModel{}
	m.Ips = ip_helper.GetDeviceAllIP("")
	m.Token = token
	b, _ := json.Marshal(m)

	if reflect.DeepEqual(IpInfo, m) {
		return
	}
	head := make(map[string]string)
	infoS := httper2.Post(config.ServerInfo.Handshake+"/v1/update", b, "application/json", head)
	fmt.Println(infoS)
}
func (p *personService) GetPersionInfo(token string) (m model.PersionModel, err error) {
	infoS := httper2.Get(config.ServerInfo.Handshake+"/v1/ips/"+token, nil)
	err = json.Unmarshal([]byte(infoS), &m)
	return
}
func (p *personService) GetPersionNetWorkTypeDetection() string {
	data := make(chan string)
	list := []string{"stun.l.google.com", "stun1.l.google.com", "stun2.l.google.com", "stun.sipgate.net"}
	for _, v := range list {
		go utils.GetNetWorkTypeDetection(data, v)
	}
	result := <-data
	close(data)
	return result
}

func NewPersonService(db *gorm.DB) PersonService {
	return &personService{db: db}
}

//=======================================================================================================================================================================

var StreamList map[string]quic.Stream
var ServiceMessage chan model.MessageModel

func UDPService() {
	port := 0
	if len(config.ServerInfo.UDPPort) > 0 {
		port, _ = strconv.Atoi(config.ServerInfo.UDPPort)
		if port != 0 && !port2.IsPortAvailable(port, "udp") {
			port = 0
		}
	}

	srcAddr := &net.UDPAddr{
		IP: net.IPv4zero, Port: port}
	var err error
	UDPConn, err = net.ListenUDP("udp", srcAddr)
	if err != nil {
		fmt.Println(err)
	}
	listener, err := quic.Listen(UDPConn, quic_helper.GetGenerateTLSConfig(config.ServerInfo.Token), quic_helper.GetQUICConfig())
	if err != nil {
		fmt.Println(err)
	}
	defer listener.Close()
	ctx := context.Background()
	acceptFailures := 0
	const maxAcceptFailures = 10
	if err != nil {
		panic(err)
	}

	for {
		select {
		case <-ctx.Done():
			fmt.Println(ctx.Err())
			return
		default:
		}

		session, err := listener.Accept(ctx)
		if err != nil {
			fmt.Println("Listen (BEP/quic): Accepting connection:", err)

			acceptFailures++
			if acceptFailures > maxAcceptFailures {
				// Return to restart the listener, because something
				// seems permanently damaged.
				fmt.Println(err)
				return
			}

			// Slightly increased delay for each failure.
			time.Sleep(time.Duration(acceptFailures) * time.Second)

			continue
		}

		acceptFailures = 0

		streamCtx, cancel := context.WithTimeout(ctx, time.Second*10)
		stream, err := session.AcceptStream(streamCtx)
		cancel()
		if err != nil {
			fmt.Println("failed to accept stream from %s: %v", session.RemoteAddr(), err)
			_ = session.CloseWithError(1, err.Error())
			continue
		}

		// prefixByte := make([]byte, 4)
		// c1, err := io.ReadFull(stream, prefixByte)
		// fmt.Println(c1, err)
		// prefixLength, err := strconv.Atoi(string(prefixByte))
		// if err != nil {
		// 	fmt.Println(err)
		// }
		// messageByte := make([]byte, prefixLength)
		// t, err := io.ReadFull(stream, messageByte)
		// fmt.Println(t, err)
		// m := model.MessageModel{}
		// err = json.Unmarshal(messageByte, &m)
		// if err != nil {
		// 	fmt.Println(err)
		// }

		go ProcessingContent(stream)
	}
}

//处理内容
func ProcessingContent(stream quic.Stream) {
	for {
		prefixByte := make([]byte, 6)
		_, err := io.ReadFull(stream, prefixByte)
		if err != nil {
			fmt.Println(err)
			return
		}
		prefixLength, err := strconv.Atoi(string(prefixByte))
		if err != nil {
			fmt.Println(err)
		}
		messageByte := make([]byte, prefixLength)
		_, err = io.ReadFull(stream, messageByte)
		if err != nil {
			return
		}
		m := model.MessageModel{}
		err = json.Unmarshal(messageByte, &m)
		if err != nil {
			fmt.Println(err)
		}
		if m.Type == types.PERSONHELLO {
			//nothing
			continue
		} else if m.Type == types.PERSONDIRECTORY {
			friend := model2.FriendModel{}
			friend.Token = m.From
			var list []model.Path
			rFriend := MyService.Friend().GetFriendById(friend)
			if !reflect.DeepEqual(rFriend, model2.FriendModel{Token: m.From}) && !rFriend.Block {
				if m.Data.(string) == "" || m.Data.(string) == "/" {
					for _, v := range config.FileSettingInfo.ShareDir {
						//tempList := MyService.ZiMa().GetDirPath(v)
						temp := MyService.System().GetDirPathOne(v)
						list = append(list, temp)
					}
				} else {
					list = MyService.System().GetDirPath(m.Data.(string))
				}
			} else {
				list = []model.Path{}
			}
			if rFriend.Write {
				for i := 0; i < len(list); i++ {
					list[i].Write = true
				}
			}
			m.To = m.From
			m.Data = list
			m.From = config.ServerInfo.Token
			SendData(stream, m)
			break
		} else if m.Type == types.PERSONDOWNLOAD {

			SendFileData(stream, m.Data.(string), m.From, m.UUId, types.PERSONDOWNLOAD)
			break
		} else if m.Type == types.PERSONADDFRIEND {
			friend := model2.FriendModel{}
			dataModelByte, _ := json.Marshal(m.Data)
			err := json.Unmarshal(dataModelByte, &friend)
			if err != nil {
				fmt.Println(err)
				continue
			}
			go MyService.Friend().UpdateOrCreate(friend)
			mi := model2.FriendModel{}
			mi.Avatar = config.UserInfo.Avatar
			mi.Profile = config.UserInfo.Description
			mi.NickName = config.UserInfo.NickName
			m.To = m.From
			m.Data = mi
			m.Type = types.PERSONADDFRIEND
			m.From = config.ServerInfo.Token

			SendData(stream, m)
			break
		} else if m.Type == types.PERSONCONNECTION {
			if len(m.Data.(string)) > 0 {
				fmt.Println("设置ip", m.Data.(string))
				UDPAddressMap[m.From] = m.Data.(string)
			} else {
				delete(UDPAddressMap, m.From)
			}
			// mi := model2.FriendModel{}
			// mi.Avatar = config.UserInfo.Avatar
			// mi.Profile = config.UserInfo.Description
			// mi.NickName = config.UserInfo.NickName
			// mi.Token = config.ServerInfo.Token

			user := MyService.Casa().GetUserInfoByShareId(m.From)
			//好友申请 //不是好友
			friend := model2.FriendModel{}
			friend.Token = m.From
			friend.Avatar = user.Avatar
			friend.Block = false
			friend.NickName = user.NickName
			friend.Profile = user.Avatar
			friend.Write = false
			friend.Version = user.Version
			if len(config.UserInfo.Public) > 0 {
				friend.State = types.FRIENDSTATEREQUEST
			}
			MyService.Friend().AddFriend(friend)

			msg := model.MessageModel{}
			msg.Type = types.PERSONHELLO
			msg.Data = ""
			msg.To = m.From
			msg.From = config.ServerInfo.Token
			msg.UUId = m.UUId
			Dial(msg, false)

			//agree user
			if len(config.UserInfo.Public) == 0 {
				msg.Type = types.PERSONAGREEFRIEND
				msg.Data = ""
				msg.To = m.From
				msg.From = config.ServerInfo.Token
				msg.UUId = m.UUId
				Dial(msg, true)
			}
			break
		} else if m.Type == types.PERSONAGREEFRIEND {
			MyService.Friend().AgreeFrined(m.From)
			break
		} else if m.Type == types.PERSONCANCEL {
			CancelList[m.UUId] = "cancel"
			break
		} else if m.Type == types.PERSONSUMMARY {
			Summary(m, "upload")
			continue
		} else if m.Type == types.PERSONUPLOAD {
			//TODO:检查是否存在如果存在直接结束
			task := model2.PersonDownloadDBModel{}
			task.UUID = m.UUId
			task.LocalPath = m.Data.(string)
			MyService.Download().AddDownloadTask(task)
			friend := MyService.Friend().GetFriendById(model2.FriendModel{Token: m.From})
			if friend.Write {
				continue
			} else {
				break
			}
		} else if m.Type == types.PERSONUPLOADDATA {
			r := SaveFile(m, stream)
			if r {
				break
			}
			continue
		} else if m.Type == types.PERSONINTERNALINSPECTION {
			fmt.Println("内网测试")
			var ips []string
			dataModelByte, _ := json.Marshal(m.Data)
			err := json.Unmarshal(dataModelByte, &ips)
			if err != nil {
				fmt.Println(err)
				break
			}

			go MyService.Friend().InternalInspection(ips, m.From)

		} else if m.Type == types.PERSONPING {
			fmt.Println("来自", m.From, "的ping", m.Data)
			msg := m
			m.To = m.From
			m.Data = config.ServerInfo.Token
			m.From = config.ServerInfo.Token
			SendData(stream, m)

			var ips []string
			dataModelByte, _ := json.Marshal(msg.Data)
			err := json.Unmarshal(dataModelByte, &ips)
			if err != nil {
				fmt.Println(err)
				break
			}
			backIP := false
			if v, ok := UDPAddressMap[msg.From]; ok {
				for _, ip := range ips {
					if ip == v {
						backIP = true
						break
					}
				}
			}
			if !backIP {
				fmt.Println("检测需要查询ip", msg.From)
				go MyService.Friend().InternalInspection(ips, msg.From)
			}

			break
		} else if m.Type == types.PERSONIMAGETHUMBNAIL {
			m.To = m.From

			if data, err := file.GetImage(m.Data.(string), 100, 0); err == nil {
				m.Data = data
			} else {
				m.Data = ""
			}
			m.From = config.ServerInfo.Token
			SendData(stream, m)
			break
		} else {
			//不应有不做返回的数据
			//ServiceMessage <- m
			break
		}
	}
	stream.Close()

}

//文件分片发送
func SendFileData(stream quic.Stream, filePath, to, uuid, t string) error {
	summary := model.FileSummaryModel{}

	msg := model.MessageModel{}
	msg.Type = types.PERSONSUMMARY
	msg.From = config.ServerInfo.Token
	msg.To = to
	msg.UUId = uuid

	fStat, err := os.Stat(filePath)
	if err != nil {

		summary.Message = err.Error()

		msg.Data = summary

		summaryByte, _ := json.Marshal(msg)
		summaryPrefixLength := file.PrefixLength(len(summaryByte))
		summaryData := append(summaryPrefixLength, summaryByte...)
		stream.Write(summaryData)
		return err
	}

	blockSize, length := file.GetBlockInfo(fStat.Size())

	f, err := os.Open(filePath)
	if err != nil {

		summary.Message = err.Error()
		msg.Data = summary

		summaryByte, _ := json.Marshal(msg)
		summaryPrefixLength := file.PrefixLength(len(summaryByte))
		summaryData := append(summaryPrefixLength, summaryByte...)
		stream.Write(summaryData)
		return err
	}

	//send file summary first
	summary.BlockSize = blockSize
	summary.Hash = file.GetHashByPath(filePath)
	summary.Length = length
	summary.Name = fStat.Name()
	summary.Size = fStat.Size()

	msg.Data = summary

	summaryByte, _ := json.Marshal(msg)
	summaryPrefixLength := file.PrefixLength(len(summaryByte))
	summaryData := append(summaryPrefixLength, summaryByte...)
	stream.Write(summaryData)

	bufferedReader := bufio.NewReader(f)
	buf := make([]byte, blockSize)

	defer stream.Close()

	for i := 0; i < length; i++ {

		tran := model.TranFileModel{}

		n, err := bufferedReader.Read(buf)

		if err == io.EOF {
			fmt.Println("读取完毕", err)
		}

		tran.Hash = file.GetHashByContent(buf[:n])
		tran.Index = i
		tran.Length = length

		fileMsg := model.MessageModel{}
		fileMsg.Type = t
		fileMsg.Data = tran
		fileMsg.From = config.ServerInfo.Token
		fileMsg.To = to
		fileMsg.UUId = uuid
		b, _ := json.Marshal(fileMsg)
		prefixLength := file.PrefixLength(len(b))
		dataLength := file.DataLength(len(buf[:n]))
		data := append(append(append(prefixLength, b...), dataLength...), buf[:n]...)
		if _, ok := CancelList[uuid]; ok {
			delete(CancelList, uuid)
			return nil
		}
		stream.Write(data)
	}
	record := model2.PersonDownRecordDBModel{}
	record.UUID = uuid
	record.Name = f.Name()
	record.Downloader = to
	record.Path = filePath
	record.Size = fStat.Size()
	record.Type = types.PERSONFILEDOWNLOAD
	if t == types.PERSONUPLOADDATA {
		record.Type = types.PERSONFILEUPLOAD
	}

	MyService.DownRecord().AddDownRecord(record)

	return nil
}
