package node

import (
	"bytes"
	"context"
	"encoding/gob"
	"net/http"
	"os"
	"path"
	"slices"
	"sort"
	"time"

	"gopkg.in/yaml.v3"

	"github.com/bestruirui/bestsub/internal/config"
	"github.com/bestruirui/bestsub/internal/core/mihomo"
	"github.com/bestruirui/bestsub/internal/core/task"
	"github.com/bestruirui/bestsub/internal/database/op"
	nodeModel "github.com/bestruirui/bestsub/internal/models/node"
	"github.com/bestruirui/bestsub/internal/models/setting"
	"github.com/bestruirui/bestsub/internal/utils/log"
)

func InitNodePool(size int) {
	pool = make([]nodeModel.Data, 0, size)
	nodeExist = NewExist(size)
	nodeProcess = NewExist(size)
	sessionFile := config.Base().Session.NodePath
	if _, err := os.Stat(sessionFile); os.IsNotExist(err) {
		return
	}

	file, err := os.Open(sessionFile)
	if err != nil {
		return
	}
	defer file.Close()

	decoder := gob.NewDecoder(file)
	if err := decoder.Decode(&pool); err != nil {
		log.Warnf("restore node pool failed: %v", err)
		os.Remove(sessionFile)
		return
	}
	for _, node := range pool {
		nodeExist.Add(node.Base.UniqueKey)
	}
}

func CloseNodePool() error {
	var buf bytes.Buffer
	encoder := gob.NewEncoder(&buf)
	if err := encoder.Encode(pool); err != nil {
		log.Warnf("save node pool failed: %v", err)
		return err
	}
	filePath := config.Base().Session.NodePath

	dir := path.Dir(filePath)
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		os.MkdirAll(dir, 0755)
	}
	if os.WriteFile(filePath, buf.Bytes(), 0600) != nil {
		log.Warnf("node pool save failed")
	}
	log.Debugf("node pool saved")

	return nil
}

type nameNode struct {
	Name string
}

func Add(node *[]nodeModel.Base) int {
	var nodesToProcess []nodeModel.Base

	for _, n := range *node {
		var nameNode nameNode
		if err := yaml.Unmarshal(n.Raw, &nameNode); err != nil {
			log.Warnf("yaml.Unmarshal failed: %v", err)
			continue
		}
		if !nodeExist.Exist(n.UniqueKey) && !nodeProcess.Exist(n.UniqueKey) {
			nodeProcess.Add(n.UniqueKey)
			nodesToProcess = append(nodesToProcess, n)
			log.Debugf("add process node: %s", nameNode.Name)
		} else {
			log.Debugf("node already exist: %s", nameNode.Name)
		}
	}

	log.Debugf("add %d nodes to process", len(nodesToProcess))

	if len(nodesToProcess) > 0 {
		go func() {
			for _, node := range nodesToProcess {
				n := node // capture loop variable
				wgSync.Add(1)
				task.Submit(func() {
					defer wgSync.Done()
					defer nodeProcess.Remove(n.UniqueKey)
					var raw map[string]any
					if err := yaml.Unmarshal(n.Raw, &raw); err != nil {
						log.Warnf("yaml.Unmarshal failed: %v", err)
						return
					}
					client := mihomo.Proxy(raw)
					if client == nil {
						return
					}
					defer client.Release()
					ctx, cancel := context.WithTimeout(context.Background(), time.Duration(op.GetSettingInt(setting.NODE_TEST_TIMEOUT))*time.Second)
					defer cancel()
					request, err := http.NewRequestWithContext(ctx, "GET", op.GetSettingStr(setting.NODE_TEST_URL), nil)
					if err != nil {
						return
					}
					start := time.Now()
					response, err := client.Do(request)
					if err != nil {
						return
					}
					defer response.Body.Close()
					if response.StatusCode != 204 {
						return
					}

					var info nodeModel.Info
					info.Delay.Update(uint16(time.Since(start).Milliseconds()))
					info.SetAliveStatus(nodeModel.Alive, true)
					rawCopy := append([]byte(nil), n.Raw...)
					n.Raw = rawCopy
					validMutex.Lock()
					validNodes = append(validNodes, nodeModel.Data{
						Base: n,
						Info: &info,
					})
					log.Debugf("node: %s test end, Delay: %d", raw["name"].(string), info.Delay.Average())
					validMutex.Unlock()
				})

			}
		}()
		if !wgStatus {
			wgStatus = true
			go func() {
				time.Sleep(time.Second * 5)
				wgSync.Wait()
				mergedNodes := 0
				if len(validNodes) > 0 {
					mergedNodes = mergeNodesToPool(validNodes)
					RefreshInfo()
				}
				log.Infof("Receipt successful, %d new nodes added", mergedNodes)
				validNodes = validNodes[:0]
				wgStatus = false
			}()
		}
	}
	return len(nodesToProcess)
}

func ForEach(fn func(node []byte)) {
	poolMutex.RLock()
	defer poolMutex.RUnlock()
	for _, node := range pool {
		fn(node.Raw)
	}
}

func GetAll() []nodeModel.Data {
	poolMutex.RLock()
	defer poolMutex.RUnlock()
	return pool
}

func GetBySubIdExclude(subId []uint16) []uint16 {
	poolMutex.RLock()
	defer poolMutex.RUnlock()
	var result []uint16
	for _, node := range pool {
		if !slices.Contains(subId, node.Base.SubId) {
			result = append(result, node.Base.SubId)
		}
	}
	return result
}

func GetBySubId(subId []uint16) *[]nodeModel.Data {
	poolMutex.RLock()
	defer poolMutex.RUnlock()
	var result []nodeModel.Data
	for _, node := range pool {
		if slices.Contains(subId, node.Base.SubId) {
			result = append(result, node)
		}
	}
	return &result
}

func GetByFilter(filter nodeModel.Filter) *[]nodeModel.Data {
	poolMutex.RLock()
	defer poolMutex.RUnlock()
	var result []nodeModel.Data
	for _, node := range pool {
		if len(filter.SubId) > 0 {
			if filter.SubIdExclude && slices.Contains(filter.SubId, node.Base.SubId) {
				continue
			}
			if !filter.SubIdExclude && !slices.Contains(filter.SubId, node.Base.SubId) {
				continue
			}
		}
		if filter.AliveStatus != 0 && node.Info.AliveStatus&filter.AliveStatus != filter.AliveStatus {
			continue
		}
		if len(filter.Country) > 0 {
			if filter.CountryExclude && slices.Contains(filter.Country, node.Info.Country) {
				continue
			}
			if !filter.CountryExclude && !slices.Contains(filter.Country, node.Info.Country) {
				continue
			}
		}
		if filter.SpeedUpMore != 0 && node.Info.SpeedUp.Average() < filter.SpeedUpMore {
			continue
		}
		if filter.SpeedDownMore != 0 && node.Info.SpeedDown.Average() < filter.SpeedDownMore {
			continue
		}
		if filter.DelayLessThan != 0 && node.Info.Delay.Average() > filter.DelayLessThan {
			continue
		}
		if filter.RiskLessThan != 0 && node.Info.Risk > filter.RiskLessThan {
			continue
		}
		result = append(result, node)
	}
	return &result
}

func mergeNodesToPool(newNodes []nodeModel.Data) int {
	sort.Slice(newNodes, func(i, j int) bool {
		return newNodes[i].Info.Delay.Average() < newNodes[j].Info.Delay.Average()
	})

	poolMutex.Lock()
	defer poolMutex.Unlock()

	poolLen := len(pool)
	poolCap := cap(pool)

	if poolLen < poolCap {
		remainingCap := poolCap - poolLen
		if len(newNodes) < remainingCap {
			pool = append(pool, newNodes...)
			for _, node := range newNodes {
				nodeExist.Add(node.Base.UniqueKey)
			}
			return len(newNodes)
		} else {
			pool = append(pool, newNodes[:remainingCap]...)
			for _, node := range newNodes[:remainingCap] {
				nodeExist.Add(node.Base.UniqueKey)
			}
			newNodes = newNodes[remainingCap:]
		}
	}

	sort.Slice(pool, func(i, j int) bool {
		return pool[i].Info.Delay.Average() < pool[j].Info.Delay.Average()
	})

	newNodeIndex := 0
	for i := len(pool) - 1; i >= 0 && newNodeIndex < len(newNodes); i-- {
		if newNodes[newNodeIndex].Info.Delay.Average() < pool[i].Info.Delay.Average() {
			log.Debugf("new node delay %dms < old delay %dms,merge", newNodes[newNodeIndex].Info.Delay.Average(), pool[i].Info.Delay.Average())
			nodeExist.Remove(pool[i].Base.UniqueKey)
			pool[i] = newNodes[newNodeIndex]
			nodeExist.Add(newNodes[newNodeIndex].Base.UniqueKey)
			newNodeIndex++
		} else {
			log.Debugf("new node delay %dms > old delay %dms,not merge", newNodes[newNodeIndex].Info.Delay.Average(), pool[i].Info.Delay.Average())
			return newNodeIndex
		}
	}
	return 0
}

func GetSubInfo(subID uint16) nodeModel.SimpleInfo {
	refreshMutex.Lock()
	defer refreshMutex.Unlock()
	return subInfoMap[subID]
}

func GetCountryInfo(country string) nodeModel.SimpleInfo {
	refreshMutex.Lock()
	defer refreshMutex.Unlock()
	return countryInfoMap[country]
}
func DeleteBySubId(subID uint16) {
	poolMutex.Lock()
	defer poolMutex.Unlock()
	
	end := len(pool) - 1
	for i := 0; i <= end; {
		if pool[i].Base.SubId == subID {
			nodeExist.Remove(pool[i].Base.UniqueKey)
			pool[i] = pool[end]
			end--
		} else {
			i++
		}
	}
	
	pool = pool[:end+1]
}
