package service

import (
    "encoding/json"
    "strings"
    "sync"
    "time"

	"safesight-control/internal/config"
	"safesight-control/internal/models"
)

type RegistryService struct {
    cfg       *config.Config
    agent     *AgentClient
    discovery *DiscoveryService
    repo      DeviceRepository
    mu        sync.RWMutex
    devices   map[string]*models.Device
}

type DeviceRepository interface {
    Upsert(dev *models.Device) error
    List() ([]*models.Device, error)
}

func NewRegistryService(cfg *config.Config, agent *AgentClient, repo ...DeviceRepository) *RegistryService {
    var deviceRepo DeviceRepository
    if len(repo) > 0 {
        deviceRepo = repo[0]
    }
    s := &RegistryService{
        cfg:     cfg,
        agent:   agent,
        repo:    deviceRepo,
        devices: make(map[string]*models.Device),
    }
    // Load persisted devices from DB so offline devices still appear in the list
    if deviceRepo != nil {
        if saved, err := deviceRepo.List(); err == nil {
            for _, dev := range saved {
                if dev == nil || dev.DeviceID == "" {
                    continue
                }
                dev.Online = false // mark offline until discovery confirms
                s.devices[dev.DeviceID] = dev
            }
        }
    }
    go s.startPruning()
    if agent != nil {
        go s.startGraphPolling()
    }
    go s.startDiscoveryPolling()
    return s
}

func (s *RegistryService) SetDiscovery(d *DiscoveryService) {
    s.discovery = d
}

func (s *RegistryService) startDiscoveryPolling() {
    // 启动后等 3 秒再开始第一次发现，避免和 startup 的 UDP 冲突
    time.Sleep(3 * time.Second)
    // First immediate discovery
    s.runDiscovery()
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    for range ticker.C {
        s.runDiscovery()
    }
}

func (s *RegistryService) runDiscovery() {
    s.mu.RLock()
    d := s.discovery
    s.mu.RUnlock()
    if d == nil {
        return
    }
    d.SearchDefault()
}

func (s *RegistryService) startGraphPolling() {
    ticker := time.NewTicker(30 * time.Second) // Pull every 30s
    for range ticker.C {
        s.mu.RLock()
        var onlineDevices []*models.Device
        for _, dev := range s.devices {
            if dev.Online {
                onlineDevices = append(onlineDevices, dev)
            }
        }
        s.mu.RUnlock()

        for _, dev := range onlineDevices {
            data, _, err := s.agent.Do("GET", dev.IP, dev.AgentPort, "/v1/graphs", nil)
            if err == nil {
                var graphs interface{}
                if err := json.Unmarshal(data, &graphs); err == nil {
                    s.mu.Lock()
                    dev.Graphs = graphs
                    s.mu.Unlock()
                    s.persistDevice(dev)
                }
            }
        }
    }
}

func (s *RegistryService) UpdateDevice(dev *models.Device) {
    s.mu.Lock()
    defer s.mu.Unlock()

    dev.LastSeenMs = time.Now().UnixMilli()
    dev.Online = true
    if current, ok := s.devices[dev.DeviceID]; ok && strings.TrimSpace(current.DeviceAlias) != "" {
        dev.DeviceAlias = strings.TrimSpace(current.DeviceAlias)
    } else if s.repo != nil {
        if saved, err := s.repo.List(); err == nil {
            for _, item := range saved {
                if item != nil && item.DeviceID == dev.DeviceID && strings.TrimSpace(item.DeviceAlias) != "" {
                    dev.DeviceAlias = strings.TrimSpace(item.DeviceAlias)
                    break
                }
            }
        }
    }
    s.devices[dev.DeviceID] = dev
    s.persistDevice(dev)
}

func (s *RegistryService) SetDeviceAlias(deviceID string, alias string) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    alias = strings.TrimSpace(alias)
    if dev, ok := s.devices[deviceID]; ok {
        dev.DeviceAlias = alias
        s.persistDevice(dev)
        return nil
    }
    s.persistDevice(&models.Device{DeviceID: deviceID, DeviceAlias: alias})
    return nil
}

func (s *RegistryService) GetDevices() []*models.Device {
    s.mu.RLock()
    defer s.mu.RUnlock()

    list := make([]*models.Device, 0, len(s.devices))
    for _, dev := range s.devices {
        list = append(list, dev)
    }
    return list
}

// TouchDevice updates the LastSeenMs for a device to keep it online
// when accessed via HTTP API (not just UDP discovery)
func (s *RegistryService) TouchDevice(deviceID string) {
    s.mu.Lock()
    defer s.mu.Unlock()

    if dev, ok := s.devices[deviceID]; ok {
        dev.LastSeenMs = time.Now().UnixMilli()
        dev.Online = true
        s.persistDevice(dev)
    }
}

func (s *RegistryService) startPruning() {
    ticker := time.NewTicker(2 * time.Second)
    for range ticker.C {
        s.mu.Lock()
        now := time.Now().UnixMilli()
        for _, dev := range s.devices {
            if now-dev.LastSeenMs > int64(s.cfg.OfflineAfterMs) {
                dev.Online = false
            }
        }
        s.mu.Unlock()
    }
}

func (s *RegistryService) persistDevice(dev *models.Device) {
    if s == nil || s.repo == nil || dev == nil {
        return
    }
    _ = s.repo.Upsert(dev)
}
