"""Tests for PSA/ticket note export format.

Covers: all fields present, missing optional fields, duration calculation,
incomplete sessions, and edge cases.
"""

from datetime import datetime, timezone
from unittest.mock import MagicMock

import pytest

from app.schemas.session import SessionExport
from app.services.export_service import (
    generate_psa_export, generate_text_export, generate_markdown_export,
    generate_html_export, _format_duration,
)


def _make_session(
    tree_name="Test Tree",
    tree_description="A test problem description",
    ticket_number=None,
    client_name=None,
    decisions=None,
    scratchpad="",
    started_at=None,
    completed_at=None,
):
    """Create a mock session object for PSA export testing."""
    session = MagicMock()
    session.tree_snapshot = {"name": tree_name, "description": tree_description}
    session.ticket_number = ticket_number
    session.client_name = client_name
    session.decisions = decisions or []
    session.scratchpad = scratchpad
    session.started_at = started_at or datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc)
    session.completed_at = completed_at or datetime(2026, 1, 15, 11, 30, tzinfo=timezone.utc)
    return session


def _default_options():
    return SessionExport(format="psa", include_timestamps=True, include_tree_info=True)


class TestPsaExportAllFields:
    """Test PSA export with all fields populated."""

    def test_includes_header(self):
        session = _make_session(
            ticket_number="TK-5001",
            client_name="Contoso Corp",
        )
        result = generate_psa_export(session, _default_options())
        assert "=== TROUBLESHOOTING NOTES ===" in result
        assert "Ticket: TK-5001" in result
        assert "Client: Contoso Corp" in result

    def test_includes_tree_name_and_date(self):
        session = _make_session(tree_name="DNS Troubleshooting")
        result = generate_psa_export(session, _default_options())
        assert "Tree: DNS Troubleshooting" in result
        assert "Date: 2026-01-15 10:00" in result

    def test_includes_problem_section(self):
        session = _make_session(tree_description="Client cannot resolve DNS names")
        result = generate_psa_export(session, _default_options())
        assert "--- PROBLEM ---" in result
        assert "Client cannot resolve DNS names" in result

    def test_includes_steps_taken(self):
        session = _make_session(decisions=[
            {"question": "Is the DNS service running?", "answer": "Yes", "notes": "Checked services"},
            {"question": "Can you ping the DNS server?", "answer": "No"},
        ])
        result = generate_psa_export(session, _default_options())
        assert "--- STEPS TAKEN ---" in result
        assert "1. Is the DNS service running? -> Yes" in result
        assert "   Notes: Checked services" in result
        assert "2. Can you ping the DNS server? -> No" in result

    def test_includes_resolution(self):
        session = _make_session(decisions=[
            {"question": "Check cable", "answer": "Connected"},
            {"question": "Restart service", "answer": "Service restarted successfully"},
        ])
        result = generate_psa_export(session, _default_options())
        assert "--- RESOLUTION ---" in result
        assert "Service restarted successfully" in result

    def test_includes_duration(self):
        session = _make_session(
            started_at=datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc),
            completed_at=datetime(2026, 1, 15, 11, 30, tzinfo=timezone.utc),
        )
        result = generate_psa_export(session, _default_options())
        assert "--- TIME SPENT ---" in result
        assert "Duration: 1h 30m" in result

    def test_includes_engineer_notes(self):
        session = _make_session(scratchpad="Checked firewall rules, port 53 was blocked")
        result = generate_psa_export(session, _default_options())
        assert "--- ENGINEER NOTES ---" in result
        assert "Checked firewall rules, port 53 was blocked" in result


class TestPsaExportMissingFields:
    """Test PSA export gracefully handles missing optional fields."""

    def test_missing_ticket_number(self):
        session = _make_session(ticket_number=None)
        result = generate_psa_export(session, _default_options())
        assert "Ticket: N/A" in result

    def test_missing_client_name(self):
        session = _make_session(client_name=None)
        result = generate_psa_export(session, _default_options())
        assert "Client: N/A" in result

    def test_missing_description(self):
        session = _make_session(tree_description="")
        result = generate_psa_export(session, _default_options())
        assert "No description provided." in result

    def test_empty_scratchpad(self):
        session = _make_session(scratchpad="")
        result = generate_psa_export(session, _default_options())
        assert "--- ENGINEER NOTES ---" in result
        lines = result.split("\n")
        notes_idx = lines.index("--- ENGINEER NOTES ---")
        assert lines[notes_idx + 1] == "None"

    def test_whitespace_only_scratchpad(self):
        session = _make_session(scratchpad="   \n  \n  ")
        result = generate_psa_export(session, _default_options())
        lines = result.split("\n")
        notes_idx = lines.index("--- ENGINEER NOTES ---")
        assert lines[notes_idx + 1] == "None"

    def test_no_decisions(self):
        session = _make_session(decisions=[])
        result = generate_psa_export(session, _default_options())
        assert "No steps recorded." in result
        assert "No resolution recorded." in result

    def test_decision_with_action_performed_fallback(self):
        session = _make_session(decisions=[
            {"action_performed": "Restarted the server", "answer": "Done"},
        ])
        result = generate_psa_export(session, _default_options())
        assert "1. Restarted the server -> Done" in result

    def test_decision_without_answer(self):
        session = _make_session(decisions=[
            {"question": "Check logs"},
        ])
        result = generate_psa_export(session, _default_options())
        assert "1. Check logs" in result
        # No arrow when answer is missing
        assert "->" not in result


class TestDurationCalculation:
    """Test the _format_duration helper."""

    def test_hours_and_minutes(self):
        start = datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc)
        end = datetime(2026, 1, 15, 12, 45, tzinfo=timezone.utc)
        assert _format_duration(start, end) == "2h 45m"

    def test_minutes_only(self):
        start = datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc)
        end = datetime(2026, 1, 15, 10, 25, tzinfo=timezone.utc)
        assert _format_duration(start, end) == "25 minutes"

    def test_zero_minutes(self):
        start = datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc)
        end = datetime(2026, 1, 15, 10, 0, 30, tzinfo=timezone.utc)
        assert _format_duration(start, end) == "0 minutes"

    def test_incomplete_session(self):
        start = datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc)
        assert _format_duration(start, None) == "In progress"

    def test_exact_hour(self):
        start = datetime(2026, 1, 15, 10, 0, tzinfo=timezone.utc)
        end = datetime(2026, 1, 15, 11, 0, tzinfo=timezone.utc)
        assert _format_duration(start, end) == "1h 0m"


class TestPsaExportIncompleteSession:
    """Test PSA export for sessions that are not yet completed."""

    def test_incomplete_session_shows_in_progress_duration(self):
        session = _make_session(completed_at=None)
        # Override completed_at to None explicitly
        session.completed_at = None
        result = generate_psa_export(session, _default_options())
        assert "Duration: In progress" in result

    def test_incomplete_session_with_steps(self):
        session = _make_session(
            decisions=[{"question": "First step", "answer": "Done"}],
            completed_at=None,
        )
        session.completed_at = None
        result = generate_psa_export(session, _default_options())
        assert "1. First step -> Done" in result
        assert "Duration: In progress" in result


class TestPsaExportFormat:
    """Test the overall format structure of PSA export."""

    def test_section_order(self):
        session = _make_session(
            ticket_number="TK-100",
            client_name="Acme",
            decisions=[{"question": "Step 1", "answer": "Yes"}],
            scratchpad="Some notes",
        )
        result = generate_psa_export(session, _default_options())
        sections = [
            "=== TROUBLESHOOTING NOTES ===",
            "--- PROBLEM ---",
            "--- STEPS TAKEN ---",
            "--- RESOLUTION ---",
            "--- TIME SPENT ---",
            "--- ENGINEER NOTES ---",
        ]
        positions = [result.index(s) for s in sections]
        assert positions == sorted(positions), "Sections should appear in the expected order"

    def test_format_validation_accepts_psa(self):
        """Verify the schema accepts 'psa' as a valid format."""
        export = SessionExport(format="psa")
        assert export.format == "psa"


class TestPhaseB:
    """Tests for Phase B export features: custom markers, detail levels, summary."""

    def test_custom_step_markers_psa(self):
        """Custom steps should have [CUSTOM] prefix in PSA export."""
        session = _make_session(decisions=[
            {"node_id": "node-1", "question": "Check DNS", "answer": "OK"},
            {"node_id": "custom-abc123", "question": "Check Additional Logs", "answer": "Found error"},
        ])
        options = SessionExport(format="psa")
        result = generate_psa_export(session, options)
        assert "[CUSTOM] Check Additional Logs" in result
        assert "[CUSTOM] Check DNS" not in result

    def test_custom_step_markers_markdown(self):
        """Custom steps should have [CUSTOM] prefix and subtitle in markdown."""
        session = _make_session(decisions=[
            {"node_id": "custom-xyz", "question": "Manual Check", "answer": "Done"},
        ])
        options = SessionExport(format="markdown")
        result = generate_markdown_export(session, options)
        assert "[CUSTOM] Manual Check" in result
        assert "*Custom step added by engineer*" in result

    def test_custom_step_markers_html(self):
        """Custom steps should have purple badge in HTML export."""
        session = _make_session(decisions=[
            {"node_id": "custom-xyz", "question": "Manual Check", "answer": "Done"},
        ])
        options = SessionExport(format="html")
        result = generate_html_export(session, options)
        assert "CUSTOM</span>" in result

    def test_command_output_truncation_standard(self):
        """Standard detail level truncates long command output."""
        long_output = "\n".join(f"line {i}" for i in range(20))
        session = _make_session(decisions=[
            {"node_id": "node-1", "question": "Run diagnostics", "answer": "See output",
             "command_output": long_output},
        ])
        options = SessionExport(format="text", detail_level="standard")
        result = generate_text_export(session, options)
        assert "(full output omitted — 20 lines)" in result
        assert "line 19" not in result

    def test_command_output_full_detail(self):
        """Full detail level shows all command output."""
        long_output = "\n".join(f"line {i}" for i in range(20))
        session = _make_session(decisions=[
            {"node_id": "node-1", "question": "Run diagnostics", "answer": "See output",
             "command_output": long_output},
        ])
        options = SessionExport(format="text", detail_level="full")
        result = generate_text_export(session, options)
        assert "(full output omitted" not in result
        assert "line 19" in result

    def test_truncation_short_output_unchanged(self):
        """Short command output is not truncated even in standard mode."""
        short_output = "line 1\nline 2\nline 3"
        session = _make_session(decisions=[
            {"node_id": "node-1", "question": "Check", "answer": "OK",
             "command_output": short_output},
        ])
        options = SessionExport(format="text", detail_level="standard")
        result = generate_text_export(session, options)
        assert "(full output omitted" not in result
        assert "line 3" in result

    def test_truncation_markdown_format(self):
        """Markdown format uses italic truncation marker."""
        long_output = "\n".join(f"line {i}" for i in range(20))
        session = _make_session(decisions=[
            {"node_id": "node-1", "question": "Check", "answer": "OK",
             "command_output": long_output},
        ])
        options = SessionExport(format="markdown", detail_level="standard")
        result = generate_markdown_export(session, options)
        assert "*(full output omitted — 20 lines)*" in result

    def test_truncation_html_format(self):
        """HTML format shows truncation marker (currently escaped in code block)."""
        long_output = "\n".join(f"line {i}" for i in range(20))
        session = _make_session(decisions=[
            {"node_id": "node-1", "question": "Check", "answer": "OK",
             "command_output": long_output},
        ])
        options = SessionExport(format="html", detail_level="standard")
        result = generate_html_export(session, options)
        # HTML escaping causes <em> to become &lt;em&gt; in pre/code blocks
        # This is actually correct behavior for code blocks
        assert "full output omitted" in result
        assert "20 lines" in result
        assert "line 19" not in result

    def test_summary_block_psa(self):
        """Summary block appears when include_summary is True."""
        session = _make_session()
        options = SessionExport(format="psa", include_summary=True)
        result = generate_psa_export(session, options)
        assert "--- SUMMARY ---" in result
        assert "Issue:" in result
        assert "Status:" in result

    def test_no_summary_by_default(self):
        """Summary block should not appear by default."""
        session = _make_session()
        options = SessionExport(format="psa")
        result = generate_psa_export(session, options)
        assert "--- SUMMARY ---" not in result

    def test_summary_block_markdown(self):
        """Summary block in markdown uses table format."""
        session = _make_session()
        options = SessionExport(format="markdown", include_summary=True)
        result = generate_markdown_export(session, options)
        assert "## Summary" in result
        assert "| Issue |" in result

    def test_summary_status_completed(self):
        """Completed resolved session shows Resolved status in summary."""
        session = _make_session()
        session.outcome = "resolved"
        options = SessionExport(format="psa", include_summary=True)
        result = generate_psa_export(session, options)
        assert "Status: Resolved" in result

    def test_summary_status_in_progress(self):
        """In-progress session shows step count in summary status."""
        session = _make_session(
            decisions=[{"node_id": "n1", "question": "Step 1", "answer": "Done"}],
            completed_at=None,
        )
        session.completed_at = None
        options = SessionExport(format="psa", include_summary=True)
        result = generate_psa_export(session, options)
        assert "In Progress" in result

    def test_summary_empty_fields_no_placeholders(self):
        """Empty summary fields should be blank, not show placeholders."""
        session = _make_session()
        session.outcome_notes = None
        session.next_steps = None
        options = SessionExport(format="psa", include_summary=True)
        result = generate_psa_export(session, options)
        assert "[Edit in preview]" not in result
