package preview

import (
	"encoding/json"
	"errors"
	"strings"
	"testing"

	"github.com/alexei/tinyforge/internal/store"
)

// fakeStore is a minimal in-memory store satisfying the preview.Store
// interface. Tests verify business logic without the SQLite layer.
type fakeStore struct {
	workloads map[string]store.Workload
	createErr error
}

func newFakeStore() *fakeStore {
	return &fakeStore{workloads: map[string]store.Workload{}}
}

func (f *fakeStore) GetWorkloadByID(id string) (store.Workload, error) {
	w, ok := f.workloads[id]
	if !ok {
		return store.Workload{}, errors.New("not found")
	}
	return w, nil
}

func (f *fakeStore) ListChildrenByParent(parentID string) ([]store.Workload, error) {
	out := []store.Workload{}
	for _, w := range f.workloads {
		if w.ParentWorkloadID == parentID {
			out = append(out, w)
		}
	}
	return out, nil
}

func (f *fakeStore) CreateWorkload(w store.Workload) (store.Workload, error) {
	if f.createErr != nil {
		return store.Workload{}, f.createErr
	}
	if w.ID == "" {
		w.ID = "preview-" + w.Name
	}
	f.workloads[w.ID] = w
	return w, nil
}

func (f *fakeStore) DeleteWorkload(id string) error {
	delete(f.workloads, id)
	return nil
}

func TestSlugifyBranch_StripsUnsafeChars(t *testing.T) {
	cases := []struct {
		in   string
		want string
	}{
		{"main", "main"},
		{"Feature/User-Auth", "feature-user-auth"},
		{"PR#42", "pr-42"},
		{"release/v1.2.3", "release-v1-2-3"},
		{"___", "branch"},
		{strings.Repeat("a", 50), strings.Repeat("a", 32)},
	}
	for _, c := range cases {
		got := slugifyBranch(c.in)
		if got != c.want {
			t.Errorf("slugifyBranch(%q) = %q, want %q", c.in, got, c.want)
		}
	}
}

func TestPatchSourceConfigBranch_PreservesUnknownKeys(t *testing.T) {
	src := `{"port":3000,"dockerfile_path":"Dockerfile","branch":"main","provider":"github"}`
	out, err := patchSourceConfigBranch(src, "feat/x")
	if err != nil {
		t.Fatalf("patch: %v", err)
	}
	var got map[string]any
	if err := json.Unmarshal([]byte(out), &got); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if got["branch"] != "feat/x" {
		t.Errorf("branch = %v, want feat/x", got["branch"])
	}
	if got["port"] == nil || got["dockerfile_path"] == nil || got["provider"] == nil {
		t.Errorf("unknown keys dropped: %+v", got)
	}
}

func TestPatchPublicFacesSubdomain_PrefixesSubdomains(t *testing.T) {
	faces := `[{"subdomain":"app","domain":"example.com"},{"subdomain":"","domain":"raw.example.com"}]`
	out, err := patchPublicFacesSubdomain(faces, "feat-x")
	if err != nil {
		t.Fatalf("patch: %v", err)
	}
	var got []map[string]any
	if err := json.Unmarshal([]byte(out), &got); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if got[0]["subdomain"] != "feat-x-app" {
		t.Errorf("first subdomain = %v, want feat-x-app", got[0]["subdomain"])
	}
	if got[1]["subdomain"] != "" {
		t.Errorf("empty subdomain must stay empty, got %v", got[1]["subdomain"])
	}
}

func TestMaterializeForBranch_CreatesNewWhenMissing(t *testing.T) {
	fs := newFakeStore()
	template := store.Workload{
		ID:           "tmpl-1",
		Kind:         "project",
		Name:         "myapp",
		AppID:        "app-1",
		SourceKind:   "dockerfile",
		SourceConfig: `{"branch":"main","port":3000}`,
		TriggerKind:  "git",
		PublicFaces:  `[{"subdomain":"www","domain":"x.test"}]`,
	}
	fs.workloads[template.ID] = template

	child, err := MaterializeForBranch(fs, template, "feat/login")
	if err != nil {
		t.Fatalf("materialize: %v", err)
	}
	if child.ParentWorkloadID != template.ID {
		t.Errorf("parent = %q, want %q", child.ParentWorkloadID, template.ID)
	}
	if !strings.Contains(child.Name, "feat-login") {
		t.Errorf("name = %q, want it to include slug", child.Name)
	}
	var cfg map[string]any
	if err := json.Unmarshal([]byte(child.SourceConfig), &cfg); err != nil {
		t.Fatalf("decode child source_config: %v", err)
	}
	if cfg["branch"] != "feat/login" {
		t.Errorf("child branch = %v, want feat/login", cfg["branch"])
	}
	if cfg["port"] == nil {
		t.Errorf("child should inherit template port; got %+v", cfg)
	}
	var faces []map[string]any
	if err := json.Unmarshal([]byte(child.PublicFaces), &faces); err != nil {
		t.Fatalf("decode child faces: %v", err)
	}
	if !strings.HasPrefix(faces[0]["subdomain"].(string), "feat-login-") {
		t.Errorf("face subdomain = %v, want feat-login- prefix", faces[0]["subdomain"])
	}
}

func TestMaterializeForBranch_ReusesExisting(t *testing.T) {
	fs := newFakeStore()
	template := store.Workload{
		ID:           "tmpl-1",
		Kind:         "project",
		Name:         "myapp",
		SourceKind:   "dockerfile",
		SourceConfig: `{"branch":"main"}`,
	}
	fs.workloads[template.ID] = template

	first, err := MaterializeForBranch(fs, template, "feat/x")
	if err != nil {
		t.Fatalf("first materialize: %v", err)
	}
	second, err := MaterializeForBranch(fs, template, "feat/x")
	if err != nil {
		t.Fatalf("second materialize: %v", err)
	}
	if first.ID != second.ID {
		t.Errorf("expected idempotence: got %q then %q", first.ID, second.ID)
	}
	if len(fs.workloads) != 2 {
		t.Errorf("expected exactly one preview created, store has %d", len(fs.workloads))
	}
}

func TestMaterializeForBranch_RejectsEmptyBranch(t *testing.T) {
	fs := newFakeStore()
	_, err := MaterializeForBranch(fs, store.Workload{ID: "tmpl"}, "")
	if err == nil {
		t.Fatal("expected error for empty branch")
	}
}

func TestFindPreviewForBranch_MissingReturnsFalse(t *testing.T) {
	fs := newFakeStore()
	_, ok, err := FindPreviewForBranch(fs, "tmpl", "feat/x")
	if err != nil {
		t.Fatalf("find: %v", err)
	}
	if ok {
		t.Error("expected ok=false for missing preview")
	}
}
