package portstest

import (
	"context"
	"errors"
	"testing"

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

func TestEntityRepoCreateReadUpdate(t *testing.T) {
	r := NewEntityRepo()
	ctx := context.Background()

	e, err := r.Create(ctx, ports.EntityCreateInput{
		Entity: domain.Entity{Slug: "lxc:test", Name: "test", Type: "lxc", State: "active"},
		Audit:  []ports.AuditEntry{{Action: "entity.create"}},
		Event:  &ports.Event{Type: "entity.created"},
	})
	if err != nil {
		t.Fatalf("create: %v", err)
	}
	if e.ID == "" {
		t.Fatal("create did not assign an ID")
	}

	got, err := r.BySlug(ctx, "lxc:test")
	if err != nil {
		t.Fatalf("bySlug: %v", err)
	}
	if got.ID != e.ID {
		t.Fatalf("bySlug returned %s, want %s", got.ID, e.ID)
	}

	if len(r.Audits) != 1 || r.Audits[0].Action != "entity.create" {
		t.Fatalf("audit not recorded: %+v", r.Audits)
	}
	if len(r.Events) != 1 || r.Events[0].Type != "entity.created" {
		t.Fatalf("event not recorded: %+v", r.Events)
	}

	_, err = r.Create(ctx, ports.EntityCreateInput{
		Entity: domain.Entity{Slug: "lxc:test", Name: "dup", Type: "lxc"},
	})
	if !errors.Is(err, domain.ErrConflict) && !errors.Is(err, domain.ErrAlreadyExists) {
		t.Fatalf("duplicate slug: got %v, want conflict", err)
	}
}

func TestEntityRepoSetStateCheckThenAct(t *testing.T) {
	r := NewEntityRepo()
	ctx := context.Background()
	_, err := r.Create(ctx, ports.EntityCreateInput{
		Entity: domain.Entity{Slug: "host:one", Name: "one", Type: "host", State: "active"},
	})
	if err != nil {
		t.Fatalf("create: %v", err)
	}

	_, err = r.SetState(ctx, ports.EntityTransitionInput{Slug: "host:one", From: "planned", To: "decommissioned"})
	if !errors.Is(err, domain.ErrConflict) {
		t.Fatalf("stale From: got %v, want ErrConflict", err)
	}

	e, err := r.SetState(ctx, ports.EntityTransitionInput{Slug: "host:one", From: "active", To: "decommissioned"})
	if err != nil {
		t.Fatalf("SetState: %v", err)
	}
	if e.State != "decommissioned" {
		t.Fatalf("state = %s, want decommissioned", e.State)
	}
}

func TestRecordingExecutor(t *testing.T) {
	e := &RecordingExecutor{Results: []ports.ExecResult{{Output: "first"}, {Output: "second"}}}
	res := e.Run(context.Background(), ports.Target{Host: "h", User: "root"}, "uptime", ports.ExecOpts{})
	if res.Output != "first" {
		t.Fatalf("first run output = %q", res.Output)
	}
	e.Run(context.Background(), ports.Target{Host: "h"}, "w", ports.ExecOpts{})
	e.Run(context.Background(), ports.Target{Host: "h"}, "true", ports.ExecOpts{})
	if len(e.Calls) != 3 || e.Calls[2].Command != "true" {
		t.Fatalf("calls not recorded: %+v", e.Calls)
	}
	if got := e.Run(context.Background(), ports.Target{}, "x", ports.ExecOpts{}); got.Output != "second" {
		t.Fatalf("last result should repeat, got %q", got.Output)
	}
}

func TestFakeChecker(t *testing.T) {
	c := &FakeChecker{ByKind: map[string]ports.CheckResult{
		"http": {State: "ok", Value: 200},
	}}
	res := c.Check(context.Background(), ports.CheckDef{Kind: "http"}, ports.Target{})
	if res.State != "ok" || res.Value != 200 {
		t.Fatalf("http check = %+v", res)
	}
	res = c.Check(context.Background(), ports.CheckDef{Kind: "dns"}, ports.Target{})
	if res.State != "unknown" {
		t.Fatalf("unregistered kind = %+v, want unknown", res)
	}
}

func TestSpyPublisher(t *testing.T) {
	p := &SpyPublisher{}
	if err := p.Publish(context.Background(), ports.Event{Type: "x"}); err != nil {
		t.Fatalf("publish: %v", err)
	}
	if len(p.Events) != 1 || p.Events[0].Type != "x" {
		t.Fatalf("events = %+v", p.Events)
	}
}

// Interface satisfaction guards: the fakes must implement the ports.
var (
	_ ports.EntityRepository = (*EntityRepo)(nil)
	_ ports.CommandExecutor  = (*RecordingExecutor)(nil)
	_ ports.Checker          = (*FakeChecker)(nil)
	_ ports.EventPublisher   = (*SpyPublisher)(nil)
)
