package service

import (
    "encoding/json"
    "fmt"
    "os"
    "path/filepath"
    "sort"
    "strings"
    "sync"
    "time"

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

type ResourceManagementService struct {
    repo *storage.ResourcesRepo
}

type InstalledResourceStatus struct {
    Name         string `json:"name"`
    ResourceType string `json:"resource_type"`
    SHA256       string `json:"sha256"`
    SizeBytes    int64  `json:"size_bytes"`
    UpdatedAt    int64  `json:"updated_at"`
}

type ResourceStatusCell struct {
    ResourceName string `json:"resource_name"`
    ResourceType string `json:"resource_type"`
    Status       string `json:"status"`
    Version      string `json:"version"`
}

type ResourceStatusRow struct {
    DeviceID       string                    `json:"device_id"`
    DeviceName     string                    `json:"device_name"`
    Online         bool                      `json:"online"`
    Cells          []ResourceStatusCell      `json:"cells"`
    ExtraCount     int                       `json:"extra_resource_count"`
    ExtraResources []InstalledResourceStatus `json:"extra_resources"`
}

type ResourceStatusSummary struct {
    StandardResources int `json:"standard_resources"`
    Devices           int `json:"devices"`
    CompleteDevices   int `json:"complete_devices"`
    MissingDevices    int `json:"missing_devices"`
    MismatchDevices   int `json:"mismatch_devices"`
}

type ResourceStatusBoard struct {
    Summary ResourceStatusSummary `json:"summary"`
    Rows    []ResourceStatusRow   `json:"rows"`
}

func NewResourceManagementService(repo *storage.ResourcesRepo) *ResourceManagementService {
    return &ResourceManagementService{repo: repo}
}

func (s *ResourceManagementService) SyncStandardResourcesFromDirectory(dir string) error {
    if s == nil || s.repo == nil {
        return fmt.Errorf("resources repo is not configured")
    }
    dir = filepath.Clean(strings.TrimSpace(dir))
    if dir == "" {
        return fmt.Errorf("standard resources dir is empty")
    }
    if err := os.MkdirAll(dir, 0o755); err != nil {
        return err
    }
    entries, err := os.ReadDir(dir)
    if err != nil {
        return err
    }
    for _, entry := range entries {
        if entry.IsDir() {
            // Subdirectory = resource_type; scan files inside
            resourceType := entry.Name()
            subDir := filepath.Join(dir, resourceType)
            subEntries, serr := os.ReadDir(subDir)
            if serr != nil {
                continue
            }
            for _, sub := range subEntries {
                if sub.IsDir() {
                    continue
                }
                fullPath := filepath.Join(subDir, sub.Name())
                sum, size, err := hashFile(fullPath)
                if err != nil {
                    return err
                }
                record := storage.StandardResourceRecord{
                    Name:         strings.TrimSuffix(sub.Name(), filepath.Ext(sub.Name())),
                    ResourceType: resourceType,
                    Version:      "auto",
                    SHA256:       sum,
                    SizeBytes:    size,
                    FilePath:     fullPath,
                }
                if err := s.repo.Save(record); err != nil {
                    return err
                }
            }
            continue
        }
        // Flat file: infer resource_type from filename or leave empty (legacy)
        fullPath := filepath.Join(dir, entry.Name())
        sum, size, err := hashFile(fullPath)
        if err != nil {
            return err
        }
        record := storage.StandardResourceRecord{
            Name:      strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())),
            Version:   "auto",
            SHA256:    sum,
            SizeBytes: size,
            FilePath:  fullPath,
        }
        if err := s.repo.Save(record); err != nil {
            return err
        }
    }
    return nil
}

func FetchInstalledResourceStatuses(agent *AgentClient, dev *models.Device) ([]InstalledResourceStatus, error) {
    if agent == nil || dev == nil || strings.TrimSpace(dev.IP) == "" || dev.AgentPort <= 0 {
        return nil, nil
    }
    body, status, err := agent.Do("GET", dev.IP, dev.AgentPort, "/v1/resources/status", nil)
    if err != nil {
        return nil, err
    }
    if status != 200 {
        return nil, nil // agent may not implement this endpoint yet — return empty
    }
    var resp struct {
        Resources []InstalledResourceStatus `json:"resources"`
    }
    if err := json.Unmarshal(body, &resp); err != nil {
        return nil, err
    }
    return resp.Resources, nil
}

func BuildResourceStatusBoard(standardResources []storage.StandardResourceRecord, devices []*models.Device, installed map[string][]InstalledResourceStatus) ResourceStatusBoard {
    board := ResourceStatusBoard{
        Summary: ResourceStatusSummary{
            StandardResources: len(standardResources),
            Devices:           len(devices),
        },
        Rows: make([]ResourceStatusRow, 0, len(devices)),
    }
    for _, device := range devices {
        if device == nil {
            continue
        }
        index := make(map[string]InstalledResourceStatus, len(installed[device.DeviceID]))
        for _, item := range installed[device.DeviceID] {
            index[item.Name] = item
        }
        standardIndex := make(map[string]struct{}, len(standardResources))
        for _, res := range standardResources {
            standardIndex[res.Name] = struct{}{}
        }
        row := ResourceStatusRow{
            DeviceID:       device.DeviceID,
            DeviceName:     device.DisplayName(),
            Online:         device.Online,
            Cells:          make([]ResourceStatusCell, 0, len(standardResources)),
            ExtraResources: make([]InstalledResourceStatus, 0),
        }
        hasMissing := false
        hasMismatch := false
        for _, res := range standardResources {
            cell := ResourceStatusCell{
                ResourceName: res.Name,
                ResourceType: res.ResourceType,
                Version:      res.Version,
                Status:       "missing",
            }
            if item, ok := index[res.Name]; ok {
                if strings.EqualFold(strings.TrimSpace(item.SHA256), strings.TrimSpace(res.SHA256)) {
                    cell.Status = "ok"
                } else {
                    cell.Status = "mismatch"
                    hasMismatch = true
                }
            } else {
                hasMissing = true
            }
            if cell.Status == "missing" {
                hasMissing = true
            }
            row.Cells = append(row.Cells, cell)
        }
        for _, item := range installed[device.DeviceID] {
            if _, ok := standardIndex[item.Name]; ok {
                continue
            }
            row.ExtraResources = append(row.ExtraResources, item)
        }
        sort.Slice(row.ExtraResources, func(i, j int) bool {
            return row.ExtraResources[i].Name < row.ExtraResources[j].Name
        })
        row.ExtraCount = len(row.ExtraResources)
        switch {
        case hasMismatch:
            board.Summary.MismatchDevices++
        case hasMissing:
            board.Summary.MissingDevices++
        default:
            board.Summary.CompleteDevices++
        }
        board.Rows = append(board.Rows, row)
    }
    sort.SliceStable(board.Rows, func(i, j int) bool {
        return board.Rows[i].DeviceName < board.Rows[j].DeviceName
    })
    return board
}

// FetchAndBuildResourceBoard 并发查询所有在线设备的资源状态并构建面板，总超时 5 秒。
func FetchAndBuildResourceBoard(agent *AgentClient, devices []*models.Device, standardResources []storage.StandardResourceRecord) ResourceStatusBoard {
    board := ResourceStatusBoard{
        Summary: ResourceStatusSummary{
            StandardResources: len(standardResources),
            Devices:           len(devices),
        },
        Rows: make([]ResourceStatusRow, 0, len(devices)),
    }
    if agent == nil || len(standardResources) == 0 {
        return board
    }

    installed := make(map[string][]InstalledResourceStatus)
    var mu sync.Mutex
    var wg sync.WaitGroup

    // 总超时：最多等 5 秒
    done := make(chan struct{})
    go func() {
        time.Sleep(5 * time.Second)
        close(done)
    }()

    for _, device := range devices {
        if device == nil || !device.Online {
            continue
        }
        wg.Add(1)
        go func(dev *models.Device) {
            defer wg.Done()
            items, err := FetchInstalledResourceStatuses(agent, dev)
            if err == nil {
                mu.Lock()
                installed[dev.DeviceID] = items
                mu.Unlock()
            }
        }(device)
    }

    // Wait with timeout
    waitCh := make(chan struct{})
    go func() {
        wg.Wait()
        close(waitCh)
    }()
    select {
    case <-waitCh:
    case <-done:
    }

    return BuildResourceStatusBoard(standardResources, devices, installed)
}
