package daemon

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"banger/internal/imagecat"
	"banger/internal/model"
	"banger/internal/paths"
	"banger/internal/system"
)

func TestFindOrAutoPullImageReturnsLocalWithoutPulling(t *testing.T) {
	d := &Daemon{
		layout: paths.Layout{ImagesDir: t.TempDir()},
		store:  openDaemonStore(t),
		runner: system.NewRunner(),
	}
	d.img = &ImageService{
		layout: d.layout,
		store:  d.store,
		runner: d.runner,
		bundleFetch: func(context.Context, string, imagecat.CatEntry) (imagecat.Manifest, error) {
			t.Fatal("bundleFetch should not be called when image is local")
			return imagecat.Manifest{}, nil
		},
	}
	wireServices(d)
	id, _ := model.NewID()
	if err := d.store.UpsertImage(context.Background(), model.Image{
		ID:        id,
		Name:      "my-local-image",
		CreatedAt: model.Now(),
		UpdatedAt: model.Now(),
	}); err != nil {
		t.Fatal(err)
	}
	image, err := d.vm.findOrAutoPullImage(context.Background(), "my-local-image")
	if err != nil {
		t.Fatalf("findOrAutoPullImage: %v", err)
	}
	if image.Name != "my-local-image" {
		t.Fatalf("Name = %q, want my-local-image", image.Name)
	}
}

func TestFindOrAutoPullImagePullsFromCatalog(t *testing.T) {
	imagesDir := t.TempDir()
	kernelsDir := t.TempDir()
	seedKernel(t, kernelsDir, "generic-6.12")

	pullCalls := 0
	d := &Daemon{
		layout: paths.Layout{ImagesDir: imagesDir, KernelsDir: kernelsDir},
		store:  openDaemonStore(t),
		runner: system.NewRunner(),
	}
	d.img = &ImageService{
		layout: d.layout,
		store:  d.store,
		runner: d.runner,
		bundleFetch: func(ctx context.Context, destDir string, entry imagecat.CatEntry) (imagecat.Manifest, error) {
			pullCalls++
			return stubBundleFetch(imagecat.Manifest{KernelRef: "generic-6.12"})(ctx, destDir, entry)
		},
		workSeedBuilder: stubWorkSeedBuilder,
	}
	wireServices(d)
	// "debian-bookworm" is in the embedded imagecat catalog.
	image, err := d.vm.findOrAutoPullImage(context.Background(), "debian-bookworm")
	if err != nil {
		t.Fatalf("findOrAutoPullImage: %v", err)
	}
	if image.Name != "debian-bookworm" {
		t.Fatalf("Name = %q, want debian-bookworm", image.Name)
	}
	if pullCalls != 1 {
		t.Fatalf("bundleFetch calls = %d, want 1", pullCalls)
	}
}

func TestFindOrAutoPullImageReturnsOriginalErrorWhenNotInCatalog(t *testing.T) {
	d := &Daemon{
		layout: paths.Layout{ImagesDir: t.TempDir()},
		store:  openDaemonStore(t),
		runner: system.NewRunner(),
	}
	wireServices(d)
	_, err := d.vm.findOrAutoPullImage(context.Background(), "not-in-catalog-or-store")
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("err = %v, want not-found", err)
	}
}

func TestReadOrAutoPullKernelReturnsLocalWithoutPulling(t *testing.T) {
	kernelsDir := t.TempDir()
	seedKernel(t, kernelsDir, "generic-6.12")
	d := &Daemon{layout: paths.Layout{KernelsDir: kernelsDir}}
	wireServices(d)

	entry, err := d.img.readOrAutoPullKernel(context.Background(), "generic-6.12")
	if err != nil {
		t.Fatalf("readOrAutoPullKernel: %v", err)
	}
	if entry.Name != "generic-6.12" {
		t.Fatalf("Name = %q", entry.Name)
	}
}

func TestReadOrAutoPullKernelErrorsWhenNotInCatalog(t *testing.T) {
	d := &Daemon{layout: paths.Layout{KernelsDir: t.TempDir()}}
	wireServices(d)
	_, err := d.img.readOrAutoPullKernel(context.Background(), "nonexistent-kernel")
	if err == nil || !strings.Contains(err.Error(), "not found") {
		t.Fatalf("err = %v, want not-found", err)
	}
}

// TestReadOrAutoPullKernelSurfacesNonNotExistError covers the path where
// kernelcat.ReadLocal fails for a reason other than missing entry (e.g.
// corrupt manifest); the autopull logic should NOT try to fetch in that
// case since the entry clearly exists in some broken form.
func TestReadOrAutoPullKernelSurfacesNonNotExistError(t *testing.T) {
	kernelsDir := t.TempDir()
	// Seed a manifest that doesn't match the entry's own Name field —
	// kernelcat.ReadLocal returns an error, not os.ErrNotExist.
	dir := filepath.Join(kernelsDir, "broken-kernel")
	if err := os.MkdirAll(dir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"name":"different-name"}`), 0o644); err != nil {
		t.Fatal(err)
	}
	d := &Daemon{layout: paths.Layout{KernelsDir: kernelsDir}}
	wireServices(d)
	_, err := d.img.readOrAutoPullKernel(context.Background(), "broken-kernel")
	if err == nil {
		t.Fatal("want error")
	}
	// Must not be wrapped in an "auto-pull" message — the corrupt-manifest
	// failure should surface as the primary cause.
	if strings.Contains(err.Error(), "not found in catalog") {
		t.Fatalf("err = %v, should not claim 'not in catalog'", err)
	}
	// Sanity: ensure it's not os.ErrNotExist-compatible.
	if errors.Is(err, os.ErrNotExist) {
		t.Fatalf("err = %v, should not be os.ErrNotExist", err)
	}
}
