package mcp

import (
	"bufio"
	"context"
	"strings"

	"github.com/dtoro/oikos/internal/adapters/postgres"
	"github.com/dtoro/oikos/internal/execlog"
	"github.com/dtoro/oikos/internal/remote"
)

// discoverInfraDrift compares the live Proxmox guests (pct/qm list on every
// proxmox host) against the DB graph, surfacing drift the DB-only audit
// cannot see: guests running with no entity (missing), and entities whose
// pve_id is no longer live (ghost). This is the auto-discover/validate half
// of the knowledge-graph audit skill — read-only, reaches hosts over the same
// SSH/pct path the checks use.
func discoverInfraDrift(ctx context.Context, pool *db.Pool) any {
	// 1. proxmox hosts to query.
	hostRows, err := pool.Query(ctx,
		`SELECT slug FROM entities WHERE type='proxmox-host' AND COALESCE(state,'active')='active'`)
	if err != nil {
		return map[string]any{"error": "query hosts: " + err.Error()}
	}
	var hosts []string
	for hostRows.Next() {
		var s string
		if hostRows.Scan(&s) == nil {
			hosts = append(hosts, s)
		}
	}
	hostRows.Close()

	// 2. DB guests keyed by pve_id.
	type dbGuest struct {
		Slug   string `json:"slug"`
		Type   string `json:"type"`
		PveID  string `json:"pve_id"`
		Host   string `json:"host"`
	}
	dbGuests := map[string]dbGuest{}
	gr, err := pool.Query(ctx,
		`SELECT slug, type, COALESCE(attributes->>'pve_id',''), COALESCE(attributes->>'host','')
		 FROM entities WHERE type IN ('lxc','vm')`)
	if err != nil {
		return map[string]any{"error": "query guests: " + err.Error()}
	}
	for gr.Next() {
		var g dbGuest
		if gr.Scan(&g.Slug, &g.Type, &g.PveID, &g.Host) == nil && g.PveID != "" {
			dbGuests[g.PveID] = g
		}
	}
	gr.Close()

	// 3. enumerate live guests from every host.
	live := map[string]string{} // pve_id -> "host:name"
	hostErrors := map[string]string{}
	for _, hs := range hosts {
		et, rerr := remote.ResolveExecTarget(ctx, pool, hs, sshUser)
		if rerr != nil {
			hostErrors[hs] = "resolve: " + rerr.Error()
			continue
		}
		for _, cmd := range []string{"pct list", "qm list"} {
			out, eerr := sshExecStream(ctx, et.Host, et.User, et.Wrap(cmd),
				execlog.Sink(func(string, []byte) {}))
			if eerr != nil {
				hostErrors[hs+" "+cmd] = eerr.Error()
				continue
			}
			scanIDs(out, hs, live)
		}
	}

	// 4. diff.
	var missing, ghost []string
	for id, hn := range live {
		if _, ok := dbGuests[id]; !ok {
			missing = append(missing, id+" on "+hn)
		}
	}
	for id, g := range dbGuests {
		if _, ok := live[id]; !ok {
			ghost = append(ghost, g.Slug+" (pve_id="+id+")")
		}
	}

	return map[string]any{
		"hosts_queried":   len(hosts),
		"live_guests":     len(live),
		"db_guests":       len(dbGuests),
		"missing_entities": missing, // in Proxmox, no DB entity
		"ghost_entities":  ghost,    // in DB, not live in Proxmox
		"host_errors":     hostErrors,
	}
}

// scanIDs parses `pct list` / `qm list` output (VMID ... Name) into the live map.
func scanIDs(output, hostSlug string, live map[string]string) {
	sc := bufio.NewScanner(strings.NewReader(output))
	for sc.Scan() {
		line := strings.TrimSpace(sc.Text())
		if line == "" || strings.HasPrefix(strings.ToLower(line), "vmid") {
			continue
		}
		fields := strings.Fields(line)
		if len(fields) < 2 {
			continue
		}
		id := fields[0]
		name := ""
		if len(fields) >= 4 {
			name = fields[len(fields)-1] // pct: last col is name; qm: name near end
		}
		live[id] = hostSlug + ":" + name
	}
}
