package roadmapsync

import (
	"errors"
	"testing"
)

func TestIdentityNormalizeValid(t *testing.T) {
	in := Identity{
		RoadmapMilestonePath: "  agent-roadmap/phase/p/milestones/m.md ",
		Provider:             " plane ",
		Tenant:               " general ",
		Project:              " NOMAD ",
		WorkItemID:           " NOMAD-13 ",
	}
	got, err := in.Normalize()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got.Shape != ShapeMilestone {
		t.Errorf("shape = %q, want default milestone", got.Shape)
	}
	if got.RoadmapMilestonePath != "agent-roadmap/phase/p/milestones/m.md" {
		t.Errorf("path = %q", got.RoadmapMilestonePath)
	}
	if got.Provider != "plane" || got.WorkItemID != "NOMAD-13" {
		t.Errorf("provider/work item not trimmed: %+v", got)
	}
}

func TestIdentityNormalizeRejectsMissingFields(t *testing.T) {
	cases := map[string]Identity{
		"missing provider": {
			RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md",
			WorkItemID:           "NOMAD-13",
		},
		"missing work item id": {
			RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md",
			Provider:             "plane",
		},
		"missing milestone path": {
			Provider:   "plane",
			WorkItemID: "NOMAD-13",
		},
		"task without parent": {
			Shape:                ShapeTask,
			RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md",
			Provider:             "plane",
			WorkItemID:           "NOMAD-13",
		},
		"unknown shape": {
			Shape:                Shape("epic"),
			RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md",
			Provider:             "plane",
			WorkItemID:           "NOMAD-13",
		},
	}
	for name, in := range cases {
		t.Run(name, func(t *testing.T) {
			if _, err := in.Normalize(); !errors.Is(err, ErrInvalidIdentity) {
				t.Fatalf("err = %v, want ErrInvalidIdentity", err)
			}
		})
	}
}

func TestIdentityMatches(t *testing.T) {
	base := Identity{
		RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md",
		Provider:             "plane",
		WorkItemID:           "NOMAD-13",
	}
	same := base
	same.Project = "NOMAD" // non-key field differs, still a match
	if !base.Matches(same) {
		t.Errorf("expected match ignoring non-key fields")
	}

	differentWorkItem := base
	differentWorkItem.WorkItemID = "NOMAD-99"
	if base.Matches(differentWorkItem) {
		t.Errorf("different work item id must not match")
	}

	invalid := Identity{Provider: "plane"}
	if base.Matches(invalid) {
		t.Errorf("invalid identity must not match")
	}
}

func TestIdentityMatchesStrict(t *testing.T) {
	base := Identity{
		RoadmapMilestonePath: "agent-roadmap/phase/p/milestones/m.md",
		Provider:             "plane",
		Tenant:               "general",
		Project:              "NOMAD",
		WorkItemID:           "NOMAD-13",
	}
	if !base.MatchesStrict(base) {
		t.Errorf("identical identity must strict-match")
	}

	differentTenant := base
	differentTenant.Tenant = "other-workspace"
	if base.MatchesStrict(differentTenant) {
		t.Errorf("different tenant must not strict-match")
	}

	differentProject := base
	differentProject.Project = "OTHER"
	if base.MatchesStrict(differentProject) {
		t.Errorf("different project must not strict-match")
	}

	// Matches (loose) ignores tenant/project; MatchesStrict must not.
	if !base.Matches(differentTenant) {
		t.Errorf("sanity: loose Matches should still ignore tenant")
	}
}

func TestRevisionNormalizeDefaultsDevelop(t *testing.T) {
	got := Revision{}.Normalize()
	if got.Branch != DefaultBranch {
		t.Errorf("branch = %q, want develop", got.Branch)
	}
	if !(Revision{Branch: "develop", Revision: "abc"}).OnDevelop() {
		t.Errorf("expected OnDevelop true")
	}
	if (Revision{Branch: "feature/x"}).OnDevelop() {
		t.Errorf("feature branch must not be OnDevelop")
	}
}
