package validator

import (
	"errors"
	"testing"
)

// Helpers — mirror the Rust test helpers.

func mkLookup(records ...WorkerRecord) WorkerLookup {
	return NewInMemoryWorkerLookup(records)
}

func mkWorker(id, name, status, city, state, role string) WorkerRecord {
	return WorkerRecord{
		CandidateID: id,
		Name:        name,
		Status:      status,
		City:        strPtr(city),
		State:       strPtr(state),
		Role:        strPtr(role),
	}
}

func asValidationError(err error) (*ValidationError, bool) {
	var ve *ValidationError
	if errors.As(err, &ve) {
		return ve, true
	}
	return nil, false
}

// ── Schema-level errors ──

func TestFill_WrongArtifactType_FailsSchema(t *testing.T) {
	v := NewFillValidator(mkLookup())
	_, err := v.Validate(Artifact{EmailDraft: map[string]any{}})
	ve, ok := asValidationError(err)
	if !ok {
		t.Fatalf("expected ValidationError, got %v", err)
	}
	if ve.Kind != ErrSchema || ve.Field != "artifact" {
		t.Errorf("expected schema/artifact error, got %+v", ve)
	}
}

func TestFill_MissingFillsArray_FailsSchema(t *testing.T) {
	v := NewFillValidator(mkLookup())
	_, err := v.Validate(Artifact{FillProposal: map[string]any{}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrSchema || ve.Field != "fills" {
		t.Errorf("expected schema/fills error, got %+v", ve)
	}
}

func TestFill_MissingCandidateID_FailsSchema(t *testing.T) {
	v := NewFillValidator(mkLookup())
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"fills": []any{
			map[string]any{"name": "Alice"},
		},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrSchema || ve.Field != "fills[0].candidate_id" {
		t.Errorf("expected schema/fills[0].candidate_id error, got %+v", ve)
	}
}

// ── Completeness ──

func TestFill_TargetCountMismatch_FailsCompleteness(t *testing.T) {
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder")))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"_context": map[string]any{"target_count": float64(2)},
		"fills":    []any{map[string]any{"candidate_id": "w1", "name": "Alice"}},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrCompleteness {
		t.Errorf("expected completeness error, got %+v", ve)
	}
}

// ── Cross-roster checks ──

func TestFill_PhantomID_FailsConsistency(t *testing.T) {
	// Lookup is empty → any candidate_id is "phantom" — the
	// load-bearing check for the 0→85% pattern.
	v := NewFillValidator(mkLookup())
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"fills": []any{map[string]any{"candidate_id": "phantom-id", "name": "Alice"}},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrConsistency {
		t.Errorf("expected consistency error on phantom ID, got %+v", ve)
	}
}

func TestFill_DuplicateID_FailsConsistency(t *testing.T) {
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder")))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"fills": []any{
			map[string]any{"candidate_id": "w1", "name": "Alice"},
			map[string]any{"candidate_id": "w1", "name": "Alice"},
		},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrConsistency {
		t.Errorf("expected consistency error on duplicate ID, got %+v", ve)
	}
}

func TestFill_InactiveStatus_FailsConsistency(t *testing.T) {
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "inactive", "Toledo", "OH", "Welder")))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"fills": []any{map[string]any{"candidate_id": "w1", "name": "Alice"}},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrConsistency {
		t.Errorf("expected consistency error on inactive status, got %+v", ve)
	}
}

func TestFill_Blacklist_FailsPolicy(t *testing.T) {
	w := mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder")
	w.BlacklistedClients = []string{"CLI-99"}
	v := NewFillValidator(mkLookup(w))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"_context": map[string]any{"client_id": "cli-99"}, // case-insensitive
		"fills":    []any{map[string]any{"candidate_id": "w1", "name": "Alice"}},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrPolicy {
		t.Errorf("expected policy error on blacklist, got %+v", ve)
	}
}

func TestFill_GeoMismatch_FailsConsistency(t *testing.T) {
	// Worker in Detroit, contract says Toledo.
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Detroit", "MI", "Welder")))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"_context": map[string]any{"city": "Toledo", "state": "OH"},
		"fills":    []any{map[string]any{"candidate_id": "w1", "name": "Alice"}},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrConsistency {
		t.Errorf("expected consistency error on geo mismatch, got %+v", ve)
	}
}

func TestFill_RoleMismatch_FailsConsistency(t *testing.T) {
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "active", "Toledo", "OH", "Forklift Operator")))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"_context": map[string]any{"role": "Welder"},
		"fills":    []any{map[string]any{"candidate_id": "w1", "name": "Alice"}},
	}})
	ve, _ := asValidationError(err)
	if ve == nil || ve.Kind != ErrConsistency {
		t.Errorf("expected consistency error on role mismatch, got %+v", ve)
	}
}

// ── Happy path ──

func TestFill_WellFormed_Passes(t *testing.T) {
	v := NewFillValidator(mkLookup(
		mkWorker("w1", "Alice", "active", "Toledo", "OH", "Welder"),
		mkWorker("w2", "Bob", "active", "Toledo", "OH", "Welder"),
	))
	report, err := v.Validate(Artifact{FillProposal: map[string]any{
		"_context": map[string]any{
			"target_count": float64(2),
			"city":         "Toledo",
			"state":        "OH",
			"role":         "Welder",
		},
		"fills": []any{
			map[string]any{"candidate_id": "w1", "name": "Alice"},
			map[string]any{"candidate_id": "w2", "name": "Bob"},
		},
	}})
	if err != nil {
		t.Fatalf("expected pass, got %v", err)
	}
	if len(report.Findings) != 0 {
		t.Errorf("expected zero findings, got %v", report.Findings)
	}
}

// ── Name mismatch is a Finding (warning), not an error ──

func TestFill_NameMismatch_EmitsWarning(t *testing.T) {
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice Smith", "active", "Toledo", "OH", "Welder")))
	report, err := v.Validate(Artifact{FillProposal: map[string]any{
		"fills": []any{
			map[string]any{"candidate_id": "w1", "name": "Alyssa Smith"}, // typo / outdated
		},
	}})
	if err != nil {
		t.Fatalf("name mismatch should NOT error, got %v", err)
	}
	if len(report.Findings) != 1 || report.Findings[0].Severity != SeverityWarning {
		t.Errorf("expected 1 warning finding, got %v", report.Findings)
	}
}

// ── Case-insensitive matches ──

func TestFill_CaseInsensitiveMatch_Passes(t *testing.T) {
	v := NewFillValidator(mkLookup(mkWorker("w1", "Alice", "ACTIVE", "TOLEDO", "oh", "Welder")))
	_, err := v.Validate(Artifact{FillProposal: map[string]any{
		"_context": map[string]any{"city": "Toledo", "state": "OH"},
		"fills":    []any{map[string]any{"candidate_id": "w1", "name": "Alice"}},
	}})
	if err != nil {
		t.Errorf("case-insensitive comparisons should pass, got %v", err)
	}
}

// ── Validator name is stable ──

func TestFill_NameMatchesRust(t *testing.T) {
	v := NewFillValidator(mkLookup())
	if v.Name() != "staffing.fill" {
		t.Errorf("name should match Rust 'staffing.fill', got %q", v.Name())
	}
}
