// Package nexusreporter pushes newly discovered or changed assets to IT Nexus.
package nexusreporter

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"time"

	"github.com/cereda-systems/nexus-scanner/internal/config"
	"github.com/cereda-systems/nexus-scanner/internal/db"
)

const modName = "nexus_reporter"

type Module struct {
	site   string
	cfg    config.ReporterConfig
	nexus  config.Config // full config for URL + APIKey
	store  *db.Store
	client *http.Client
	// last hash per IP to detect changes
	lastHash map[string][32]byte
}

// New creates a new IT Nexus reporter module.
func New(site string, cfg config.ReporterConfig, nexusCfg config.Config, store *db.Store) *Module {
	return &Module{
		site:     site,
		cfg:      cfg,
		nexus:    nexusCfg,
		store:    store,
		client:   &http.Client{Timeout: 15 * time.Second},
		lastHash: make(map[string][32]byte),
	}
}

func (m *Module) Name() string            { return modName }
func (m *Module) Interval() time.Duration { return m.cfg.Interval }

type assetPayload struct {
	Site      string    `json:"site"`
	Version   string    `json:"scanner_version"`
	ReportedAt time.Time `json:"reported_at"`
	Assets    []assetItem `json:"assets"`
}

type assetItem struct {
	IP        string    `json:"ip"`
	MAC       string    `json:"mac"`
	Hostname  string    `json:"hostname"`
	Vendor    string    `json:"vendor"`
	Status    string    `json:"status"`
	FirstSeen time.Time `json:"first_seen"`
	LastSeen  time.Time `json:"last_seen"`
}

// Run reports changed/new hosts to IT Nexus.
func (m *Module) Run(ctx context.Context) error {
	if m.nexus.Nexus.URL == "" || m.nexus.Nexus.APIKey == "" {
		slog.Debug("nexus reporter skipped — URL or APIKey not configured")
		return nil
	}

	hosts, err := m.store.ListHosts("")
	if err != nil {
		return fmt.Errorf("list hosts: %w", err)
	}

	var changed []assetItem
	for _, h := range hosts {
		hash := hostHash(h)
		if prev, seen := m.lastHash[h.IP]; seen && prev == hash {
			continue // unchanged
		}
		m.lastHash[h.IP] = hash
		changed = append(changed, assetItem{
			IP:        h.IP,
			MAC:       h.MAC,
			Hostname:  h.Hostname,
			Vendor:    h.Vendor,
			Status:    h.Status,
			FirstSeen: h.FirstSeen,
			LastSeen:  h.LastSeen,
		})
	}

	if len(changed) == 0 {
		slog.Debug("nexus reporter: no changes")
		return nil
	}

	payload := assetPayload{
		Site:       m.site,
		Version:    "1.0",
		ReportedAt: time.Now(),
		Assets:     changed,
	}

	body, _ := json.Marshal(payload)
	url := m.nexus.Nexus.URL + "/api/scanner/assets"
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("build request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Scanner-Key", m.nexus.Nexus.APIKey)

	resp, err := m.client.Do(req)
	if err != nil {
		return fmt.Errorf("post to nexus: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return fmt.Errorf("nexus returned HTTP %d", resp.StatusCode)
	}

	slog.Info("nexus reporter: assets reported", "count", len(changed), "url", url)
	return nil
}

func hostHash(h db.Host) [32]byte {
	s := h.IP + "|" + h.MAC + "|" + h.Hostname + "|" + h.Vendor + "|" + h.Status
	return sha256.Sum256([]byte(s))
}
