package litigationplanner

import (
	"strings"
	"testing"
)

// TestParseSpec_Roundtrip pins the spec-decoder contract: well-formed
// jsonb with version=1 parses; unknown versions and malformed JSON
// surface ErrInvalidScenario.
func TestParseSpec_Roundtrip(t *testing.T) {
	cases := []struct {
		name    string
		spec    string
		wantErr bool
	}{
		{
			"v1 primary-only",
			`{"version":1,"base_trigger_date":"2026-05-26","proceedings":[{"code":"upc.inf.cfi","role":"primary"}]}`,
			false,
		},
		{
			"v1 with full primary entry",
			`{"version":1,"base_trigger_date":"2026-05-26","proceedings":[
				{"code":"upc.inf.cfi","role":"primary","flags":["with_ccr"],
				 "anchor_overrides":{"inf.reply":"2026-08-15"},
				 "skip_rules":["inf.r30_amend"]}
			 ]}`,
			false,
		},
		{
			"v2 spec rejected — unknown version",
			`{"version":2,"proceedings":[]}`,
			true,
		},
		{
			"empty spec",
			``,
			true,
		},
		{
			"malformed json",
			`{"version":1,"proceedings":[}`,
			true,
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			_, err := ParseSpec(NullableJSON(c.spec))
			if c.wantErr && err == nil {
				t.Errorf("ParseSpec(%s): want error, got nil", c.spec)
			}
			if !c.wantErr && err != nil {
				t.Errorf("ParseSpec(%s): unexpected error %v", c.spec, err)
			}
		})
	}
}

// TestScenarioSpec_PrimaryProceeding pins the "exactly one primary"
// invariant: zero → ErrScenarioNoPrimary; multiple → ErrInvalidScenario.
func TestScenarioSpec_PrimaryProceeding(t *testing.T) {
	t.Run("zero primary → ErrScenarioNoPrimary", func(t *testing.T) {
		s := &ScenarioSpec{
			Version: 1,
			Proceedings: []ScenarioProceeding{
				{Code: "upc.inf.cfi", Role: ScenarioRolePeer},
			},
		}
		_, err := s.PrimaryProceeding()
		if err != ErrScenarioNoPrimary {
			t.Errorf("want ErrScenarioNoPrimary, got %v", err)
		}
	})

	t.Run("two primaries rejected", func(t *testing.T) {
		s := &ScenarioSpec{
			Version: 1,
			Proceedings: []ScenarioProceeding{
				{Code: "upc.inf.cfi", Role: ScenarioRolePrimary},
				{Code: "upc.rev.cfi", Role: ScenarioRolePrimary},
			},
		}
		_, err := s.PrimaryProceeding()
		if err == nil || !strings.Contains(err.Error(), "multiple proceedings with role='primary'") {
			t.Errorf("want multi-primary error, got %v", err)
		}
	})

	t.Run("single primary picked", func(t *testing.T) {
		s := &ScenarioSpec{
			Version: 1,
			Proceedings: []ScenarioProceeding{
				{Code: "upc.inf.cfi", Role: ScenarioRolePeer},
				{Code: "upc.rev.cfi", Role: ScenarioRolePrimary, Flags: []string{"with_amend"}},
			},
		}
		p, err := s.PrimaryProceeding()
		if err != nil {
			t.Fatalf("PrimaryProceeding: %v", err)
		}
		if p.Code != "upc.rev.cfi" {
			t.Errorf("primary code = %q, want upc.rev.cfi", p.Code)
		}
		if len(p.Flags) != 1 || p.Flags[0] != "with_amend" {
			t.Errorf("primary.Flags = %v, want [with_amend]", p.Flags)
		}
	})
}

// TestScenarioSpec_CalcOptionsFromSpec covers the unpack from spec
// jsonb into the CalcOptions the engine consumes. Pins:
//   - base_trigger_date used when no per-proceeding override
//   - trigger_date_override wins when set
//   - flags + anchor_overrides + appeal_target passed through verbatim
//   - per_card_choices unpacked into PerCardAppellant / SkipRules /
//     IncludeCCRFor maps
func TestScenarioSpec_CalcOptionsFromSpec(t *testing.T) {
	includeTrue := true
	skipTrue := true
	s := &ScenarioSpec{
		Version:         1,
		BaseTriggerDate: "2026-05-26",
		Proceedings: []ScenarioProceeding{{
			Code:                "upc.inf.cfi",
			Role:                ScenarioRolePrimary,
			Flags:               []string{"with_ccr"},
			AnchorOverrides:     map[string]string{"inf.reply": "2026-08-15"},
			AppealTarget:        "endentscheidung",
			SkipRules:           []string{"explicit_skip_code"},
			PerCardChoices: map[string]ScenarioCardChoice{
				"inf.r30_amend":   {Appellant: "claimant"},
				"inf.rejoin":      {IncludeCCR: &includeTrue},
				"inf.amend_other": {Skip: &skipTrue},
			},
		}},
	}
	code, td, opts, err := s.CalcOptionsFromSpec()
	if err != nil {
		t.Fatalf("CalcOptionsFromSpec: %v", err)
	}
	if code != "upc.inf.cfi" {
		t.Errorf("code = %q, want upc.inf.cfi", code)
	}
	if td != "2026-05-26" {
		t.Errorf("triggerDate = %q, want 2026-05-26", td)
	}
	if len(opts.Flags) != 1 || opts.Flags[0] != "with_ccr" {
		t.Errorf("opts.Flags = %v, want [with_ccr]", opts.Flags)
	}
	if opts.AppealTarget != "endentscheidung" {
		t.Errorf("opts.AppealTarget = %q, want endentscheidung", opts.AppealTarget)
	}
	if got := opts.AnchorOverrides["inf.reply"]; got != "2026-08-15" {
		t.Errorf("opts.AnchorOverrides[inf.reply] = %q, want 2026-08-15", got)
	}
	if got := opts.PerCardAppellant["inf.r30_amend"]; got != "claimant" {
		t.Errorf("opts.PerCardAppellant[inf.r30_amend] = %q, want claimant", got)
	}
	if _, ok := opts.IncludeCCRFor["inf.rejoin"]; !ok {
		t.Error("opts.IncludeCCRFor missing inf.rejoin")
	}
	if _, ok := opts.SkipRules["inf.amend_other"]; !ok {
		t.Error("opts.SkipRules missing inf.amend_other (from per_card_choices.skip)")
	}
	if _, ok := opts.SkipRules["explicit_skip_code"]; !ok {
		t.Error("opts.SkipRules missing explicit_skip_code (from skip_rules[])")
	}
}

// TestScenarioSpec_TriggerDateOverride pins the per-proceeding override
// path (v2-ready — primary entry honours trigger_date_override too).
func TestScenarioSpec_TriggerDateOverride(t *testing.T) {
	s := &ScenarioSpec{
		Version:         1,
		BaseTriggerDate: "2026-05-26",
		Proceedings: []ScenarioProceeding{{
			Code:                "upc.inf.cfi",
			Role:                ScenarioRolePrimary,
			TriggerDateOverride: "2026-12-01",
		}},
	}
	_, td, _, err := s.CalcOptionsFromSpec()
	if err != nil {
		t.Fatalf("CalcOptionsFromSpec: %v", err)
	}
	if td != "2026-12-01" {
		t.Errorf("triggerDate = %q, want override 2026-12-01", td)
	}
}

// TestScenarioSpec_NoBaseTrigger pins the safety check that a spec
// without base_trigger_date AND without per-proceeding override
// surfaces ErrInvalidScenario (the engine can't render without a date).
func TestScenarioSpec_NoBaseTrigger(t *testing.T) {
	s := &ScenarioSpec{
		Version: 1,
		Proceedings: []ScenarioProceeding{{
			Code: "upc.inf.cfi",
			Role: ScenarioRolePrimary,
		}},
	}
	_, _, _, err := s.CalcOptionsFromSpec()
	if err == nil {
		t.Fatal("want ErrInvalidScenario, got nil")
	}
}
