package discovery

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/wailsapp/wails/v3/pkg/application"
)

const (
	DiscoveryPort = 9988
	HeartbeatRate = 3 * time.Second
	PeerTimeout   = 10 * time.Second
)

type Service struct {
	app *application.App

	ID             string
	Name           string
	FileServerPort int

	// key 使用 peer.id 和 peer.ip 组合而成的 hash
	peers      map[string]*Peer
	peersMutex sync.RWMutex
}

func init() {
	application.RegisterEvent[[]Peer]("peers:update")
}

// getOrInitDeviceID 获取或初始化设备 ID
func getOrInitDeviceID() string {
	configDir, err := os.UserConfigDir()
	if err != nil {
		return uuid.New().String()
	}

	appDir := filepath.Join(configDir, "mesh-drop")
	if err := os.MkdirAll(appDir, 0755); err != nil {
		return uuid.New().String()
	}

	idFile := filepath.Join(appDir, "device_id")
	if data, err := os.ReadFile(idFile); err == nil {
		return string(data)
	}

	id := uuid.New().String()
	_ = os.WriteFile(idFile, []byte(id), 0644)
	return id
}

func NewService(app *application.App, name string, port int) *Service {
	return &Service{
		app:            app,
		ID:             getOrInitDeviceID(),
		Name:           name,
		FileServerPort: port,
		peers:          make(map[string]*Peer),
	}
}

func (s *Service) startBroadcasting() {
	ticker := time.NewTicker(HeartbeatRate)
	for range ticker.C {
		interfaces, err := net.Interfaces()
		if err != nil {
			slog.Error("Failed to get network interfaces", "error", err, "component", "discovery")
			continue
		}
		packet := PresencePacket{
			ID:   s.ID,
			Name: s.Name,
			Port: s.FileServerPort,
			OS:   OS(runtime.GOOS),
		}
		data, _ := json.Marshal(packet)
		for _, iface := range interfaces {
			// 过滤掉 Down 的接口和 Loopback 接口
			if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
				continue
			}
			// 获取该接口的地址
			addrs, err := iface.Addrs()
			if err != nil {
				continue
			}
			for _, addr := range addrs {
				ip, ipNet, err := net.ParseCIDR(addr.String())
				if err != nil {
					continue
				}
				if ip.To4() == nil {
					continue
				}
				// 计算该网段的广播地址
				// 例如 IP: 192.168.1.5/24 -> 广播地址: 192.168.1.255
				broadcastIPV4 := make(net.IP, len(ip.To4()))
				copy(broadcastIPV4, ip.To4())
				for i, b := range ipNet.Mask {
					broadcastIPV4[i] |= ^b
				}
				slog.Debug("Broadcast IP", "ip", broadcastIPV4.String(), "component", "discovery")
				s.sendPacketTo(broadcastIPV4.String(), DiscoveryPort, data)
			}
		}
	}
}

func (s *Service) sendPacketTo(ip string, port int, data []byte) {
	addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, port))
	if err != nil {
		return
	}
	conn, err := net.DialUDP("udp", nil, addr)
	if err != nil {
		return
	}
	defer conn.Close()
	_, err = conn.Write(data)
	if err != nil {
		slog.Error("Failed to send packet", "error", err, "component", "discovery")
		return
	}
}

func (s *Service) startListening() {
	addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", DiscoveryPort))
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		slog.Error("Failed to start listening", "error", err, "component", "discovery")
		return
	}
	defer conn.Close()

	buf := make([]byte, 1024)
	for {
		n, remoteAddr, err := conn.ReadFromUDP(buf)
		if err != nil {
			continue
		}

		var packet PresencePacket
		if err := json.Unmarshal(buf[:n], &packet); err != nil {
			continue
		}

		// 忽略自己发出的包
		if packet.ID == s.ID {
			continue
		}

		s.handleHeartbeat(packet, remoteAddr.IP.String())
	}
}

// 处理心跳包
func (s *Service) handleHeartbeat(pkt PresencePacket, ip string) {
	s.peersMutex.Lock()

	peer, exists := s.peers[pkt.ID]
	if !exists {
		// 发现新节点
		peer = &Peer{
			ID:   pkt.ID,
			Name: pkt.Name,
			Routes: map[string]*RouteState{
				ip: {
					IP:       ip,
					LastSeen: time.Now(),
				},
			},
			Port: pkt.Port,
			OS:   pkt.OS,
		}
		s.peers[peer.ID] = peer
		slog.Info("New device found", "name", pkt.Name, "ip", ip, "component", "discovery")
	} else {
		// 更新节点
		peer.Routes[ip] = &RouteState{
			IP:       ip,
			LastSeen: time.Now(),
		}
	}

	peer.IsOnline = true
	s.peersMutex.Unlock()

	// 触发前端更新 (防抖逻辑可以之后加，这里每次变动都推)
	s.app.Event.Emit("peers:update", s.GetPeers())
}

// 3. 掉线清理协程
func (s *Service) startCleanup() {
	ticker := time.NewTicker(2 * time.Second)
	for range ticker.C {
		s.peersMutex.Lock()
		changed := false
		now := time.Now()

		for id, peer := range s.peers {
			for ip, route := range peer.Routes {
				// 超过10秒没心跳，认为下线
				if now.Sub(route.LastSeen) > PeerTimeout {
					delete(peer.Routes, ip)
					changed = true
					slog.Info("Device offline", "name", peer.Name, "component", "discovery")
				}
			}

			if len(peer.Routes) == 0 {
				delete(s.peers, id)
				changed = true
				slog.Info("Device offline", "name", peer.Name, "component", "discovery")
			}
		}
		s.peersMutex.Unlock()

		if changed {
			s.app.Event.Emit("peers:update", s.GetPeers())
		}
	}
}

func (s *Service) Start() {
	go s.startBroadcasting()
	go s.startListening()
	go s.startCleanup()
}

func (s *Service) GetPeerByIP(ip string) *Peer {
	s.peersMutex.RLock()
	defer s.peersMutex.RUnlock()

	for _, p := range s.peers {
		if p.Routes[ip] != nil {
			return p
		}
	}
	return nil
}

func (s *Service) GetPeers() []Peer {
	s.peersMutex.RLock()
	defer s.peersMutex.RUnlock()

	list := make([]Peer, 0)
	for _, p := range s.peers {
		list = append(list, *p)
	}
	return list
}

func (s *Service) GetName() string {
	return s.Name
}

func (s *Service) GetID() string {
	return s.ID
}
