// Package dnsreverse implements the dns_reverse module.
// It enriches already-known hosts by performing reverse-DNS lookups
// for every host that has no hostname recorded yet.
package dnsreverse

import (
	"context"
	"fmt"
	"log/slog"
	"net"
	"strings"
	"time"

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

// Module enriches hosts in the database with reverse-DNS hostnames.
type Module struct {
	site  string
	store *db.Store
	log   *slog.Logger
}

// New returns a new dns_reverse module.
func New(site string, store *db.Store) *Module {
	return &Module{
		site:  site,
		store: store,
		log:   slog.With("module", "dns_reverse"),
	}
}

func (m *Module) Name() string            { return "dns_reverse" }
func (m *Module) Interval() time.Duration { return 10 * time.Minute }

// Run looks up hostnames for all hosts that have none recorded yet.
func (m *Module) Run(ctx context.Context) error {
	scanID, err := m.store.BeginScan(m.Name())
	if err != nil {
		return fmt.Errorf("begin scan: %w", err)
	}

	scanErr := m.scan(ctx)
	if endErr := m.store.EndScan(scanID, scanErr); endErr != nil {
		m.log.Error("record scan end", "err", endErr)
	}
	return scanErr
}

func (m *Module) scan(ctx context.Context) error {
	hosts, err := m.store.ListHosts("")
	if err != nil {
		return fmt.Errorf("list hosts: %w", err)
	}

	resolved := 0
	for _, h := range hosts {
		// Skip hosts that already have a hostname.
		if h.Hostname != "" {
			continue
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		names, err := net.DefaultResolver.LookupAddr(ctx, h.IP)
		if err != nil {
			// A lookup failure is not fatal — continue with the next host.
			m.log.Debug("reverse lookup failed", "ip", h.IP, "err", err)
			continue
		}
		if len(names) == 0 {
			continue
		}

		// LookupAddr returns FQDNs with a trailing dot — strip it.
		hostname := strings.TrimSuffix(names[0], ".")

		h.Hostname = hostname
		if err := m.store.UpsertHost(h); err != nil {
			m.log.Error("upsert host", "ip", h.IP, "err", err)
			continue
		}

		m.log.Info("hostname resolved", "ip", h.IP, "hostname", hostname)
		resolved++
	}

	m.log.Info("scan complete", "checked", len(hosts), "resolved", resolved)
	return nil
}
