package app

import (
	"context"
	"errors"
	"testing"

	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/core/ports"
	"github.com/dtoro/oikos/internal/core/ports/portstest"
)

func newProvisioningTestDeps() (*portstest.FakeProvisioner, *portstest.FakeResolver, *portstest.EntityRepo, *portstest.RelRepo, *ProvisioningService) {
	prov := &portstest.FakeProvisioner{LXCRes: ports.ProvisionResult{VMID: 142, Template: "debian-13-standard_13.0-1_amd64.tar.zst", Output: "create ok"}}
	resolver := &portstest.FakeResolver{Addr: "10.0.0.5", User: "root"}
	entities := portstest.NewEntityRepo()
	rels := portstest.NewRelRepo()
	entities.Create(context.Background(), ports.EntityCreateInput{
		Entity: domain.Entity{ID: "host-1", Slug: "host:hubris", Type: "proxmox-host", Name: "hubris", State: "active"},
	})
	svc := NewProvisioningService(prov, resolver, entities, rels)
	return prov, resolver, entities, rels, svc
}

func TestProvisioningCreateLXCRequiresHostname(t *testing.T) {
	_, _, _, _, svc := newProvisioningTestDeps()
	_, err := svc.CreateLXC(context.Background(), CreateLXCCmd{HostSlug: "host:hubris"})
	if err == nil || err.Error() != "pct_create: hostname is required" {
		t.Fatalf("got %v, want hostname-required error", err)
	}
}

func TestProvisioningCreateLXCHappyPath(t *testing.T) {
	prov, resolver, entities, rels, svc := newProvisioningTestDeps()

	out, err := svc.CreateLXC(context.Background(), CreateLXCCmd{
		HostSlug: "host:hubris",
		Hostname: "grafana",
		IP:       "192.168.8.55",
	})
	if err != nil {
		t.Fatalf("CreateLXC: %v", err)
	}

	if out.Slug != "lxc:grafana" || out.VMID != 142 {
		t.Errorf("outcome = %+v, want slug lxc:grafana vmid 142", out)
	}

	// Provisioner saw the resolved endpoint and the defaults.
	if len(prov.LXCs) != 1 {
		t.Fatalf("provisioner calls = %d, want 1", len(prov.LXCs))
	}
	in := prov.LXCs[0]
	if in.HostAddr != "10.0.0.5" || in.HostUser != "root" {
		t.Errorf("resolved endpoint = %s@%s, want root@10.0.0.5", in.HostUser, in.HostAddr)
	}
	if in.Cores != 1 || in.MemoryMB != 512 || in.DiskGB != 8 || in.Storage != "local-lvm" {
		t.Errorf("defaults not applied: %+v", in)
	}
	if in.Bridge != "vmbr0" || in.GW != "192.168.8.2" || in.Searchdomain != "hubris.network" {
		t.Errorf("network defaults not applied: %+v", in)
	}

	// Entity registered: provisioning state, enrolled, vmid/host attrs.
	got, ok := entities.FindBySlug("lxc:grafana")
	if !ok {
		t.Fatal("lxc:grafana not registered")
	}
	if got.Type != "lxc" || got.State != "provisioning" {
		t.Errorf("entity = %s/%s, want lxc/provisioning", got.Type, got.State)
	}
	if got.Attributes["pve_id"] != "142" || got.Attributes["host"] != "hubris" || got.Attributes["ip"] != "192.168.8.55" {
		t.Errorf("attrs = %v", got.Attributes)
	}
	if _, enrolled := entities.Enrolled[got.ID]; !enrolled {
		t.Error("entity not enrolled")
	}

	// Hosts edge from the Proxmox host to the guest.
	edges, err := rels.ListFor(context.Background(), domain.UUID("host-1"), "outbound")
	if err != nil || len(edges) != 1 {
		t.Fatalf("host edges = %v err=%v, want 1", edges, err)
	}
	if edges[0].Type != "hosts" || edges[0].TargetID != got.ID {
		t.Errorf("edge = %+v, want hosts → %s", edges[0], got.ID)
	}
	if edges[0].Attributes["provisioned_by"] != "nomos" {
		t.Errorf("edge attrs = %v, want provisioned_by=nomos", edges[0].Attributes)
	}

	_ = resolver // endpoint asserted via provisioner input
}

func TestProvisioningCreateLXCProvisionerFailureRegistersNothing(t *testing.T) {
	prov, _, entities, rels, svc := newProvisioningTestDeps()
	prov.LXCErr = errors.New("no usable LXC template")

	if _, err := svc.CreateLXC(context.Background(), CreateLXCCmd{HostSlug: "host:hubris", Hostname: "x"}); err == nil {
		t.Fatal("expected provisioner error to propagate")
	}
	if _, ok := entities.FindBySlug("lxc:x"); ok {
		t.Error("failed create must not register an entity")
	}
	if edges, _ := rels.ListFor(context.Background(), domain.UUID("host-1"), "outbound"); len(edges) != 0 {
		t.Errorf("failed create must not create edges, got %v", edges)
	}
}

func TestProvisioningCreateLXCRegistrationBestEffort(t *testing.T) {
	_, _, entities, rels, svc := newProvisioningTestDeps()
	entities.ErrStub = errors.New("db down")

	// The container exists on the host; a graph write failure must not
	// fail the provisioning outcome.
	out, err := svc.CreateLXC(context.Background(), CreateLXCCmd{HostSlug: "host:hubris", Hostname: "y"})
	if err != nil {
		t.Fatalf("CreateLXC should survive registration failure: %v", err)
	}
	if out.VMID != 142 {
		t.Errorf("outcome = %+v, want provisioner result", out)
	}
	if edges, _ := rels.ListFor(context.Background(), domain.UUID("host-1"), "outbound"); len(edges) != 0 {
		t.Errorf("no edges expected when entity write failed, got %v", edges)
	}
}
