# SPDX-License-Identifier: MIT
"""Contract tests for the deterministic workflow fixture."""

import json
from pathlib import Path
import sys
import unittest

sys.path.insert(0, str(Path(__file__).parent))

from workflow import (  # noqa: E402
    FailingTraceSink,
    FixtureTools,
    MemoryTraceSink,
    SupportRequest,
    run_support_workflow,
)


def fixture_request() -> SupportRequest:
    return SupportRequest(
        question="Can roman.test@example.com refund acct_DEMO123?",
        email="roman.test@example.com",
        account_id="acct_DEMO123",
    )


class WorkflowTest(unittest.TestCase):
    def test_happy_path_has_one_root_and_valid_parentage(self) -> None:
        sink = MemoryTraceSink()
        result = run_support_workflow(fixture_request(), FixtureTools(), sink)

        roots = [event for event in sink.events if event.phase == "start" and not event.parent_id]
        self.assertEqual(len(roots), 1)
        self.assertEqual(roots[0].observation_id, "task-1")
        self.assertTrue(
            all(
                event.observation_id == "task-1" or event.parent_id == "task-1"
                for event in sink.events
            )
        )
        self.assertFalse(result.needs_human_review)

    def test_retry_keeps_failed_and_successful_attempts(self) -> None:
        sink = MemoryTraceSink()
        run_support_workflow(
            fixture_request(),
            FixtureTools(subscription_failures_remaining=1),
            sink,
        )

        attempts = [
            event
            for event in sink.events
            if event.phase == "end" and event.name == "tool:get-subscription"
        ]
        self.assertEqual(
            [(event.observation_id, event.attempt, event.status) for event in attempts],
            [("subscription-1", 1, "error"), ("subscription-2", 2, "ok")],
        )

    def test_terminal_tool_error_does_not_fail_successful_siblings(self) -> None:
        sink = MemoryTraceSink()
        result = run_support_workflow(
            fixture_request(),
            FixtureTools(refund_terminal_error=True),
            sink,
        )

        end_statuses = {
            event.observation_id: event.status for event in sink.events if event.phase == "end"
        }
        self.assertEqual(end_statuses["refund-1"], "error")
        self.assertEqual(end_statuses["retrieval-1"], "ok")
        self.assertEqual(end_statuses["plan-1"], "ok")
        self.assertEqual(end_statuses["answer-1"], "ok")
        self.assertEqual(end_statuses["task-1"], "warning")
        self.assertTrue(result.needs_human_review)

    def test_trace_events_do_not_contain_synthetic_identifiers(self) -> None:
        sink = MemoryTraceSink()
        run_support_workflow(fixture_request(), FixtureTools(), sink)

        serialized = json.dumps([event.to_dict() for event in sink.events], sort_keys=True)
        self.assertNotIn("roman.test@example.com", serialized)
        self.assertNotIn("acct_DEMO123", serialized)
        self.assertIn("[EMAIL_REDACTED]", serialized)
        self.assertIn("[ACCOUNT_REDACTED]", serialized)

    def test_exporter_failure_does_not_change_application_result(self) -> None:
        expected = run_support_workflow(
            fixture_request(),
            FixtureTools(),
            MemoryTraceSink(),
        )
        actual = run_support_workflow(
            fixture_request(),
            FixtureTools(),
            FailingTraceSink(),
        )
        self.assertEqual(actual, expected)

    def test_repeated_runs_have_the_same_shape(self) -> None:
        first = MemoryTraceSink()
        second = MemoryTraceSink()
        run_support_workflow(fixture_request(), FixtureTools(), first)
        run_support_workflow(fixture_request(), FixtureTools(), second)
        self.assertEqual(first.events, second.events)


if __name__ == "__main__":
    unittest.main()
