"""Tests for save session as tree feature (Issue #17)."""
import pytest
from httpx import AsyncClient
from uuid import UUID
from datetime import datetime, timezone

from app.models.tree import Tree
from app.models.session import Session
from app.core.session_to_tree import (
    convert_session_to_tree,
    generate_tree_name_from_session,
    _find_node_in_tree
)


class TestSessionToTreeConversion:
    """Test suite for session to tree conversion logic."""

    def test_convert_empty_session(self):
        """Test converting a session with no path."""
        tree_structure = convert_session_to_tree([], {}, [], [])
        assert tree_structure["type"] == "solution"
        assert "no recorded path" in tree_structure["title"].lower()

    def test_convert_simple_linear_path(self):
        """Test converting a simple linear path."""
        tree_snapshot = {
            "id": "root",
            "type": "decision",
            "question": "Is it working?",
            "children": [
                {"id": "yes", "type": "solution", "title": "Great!"},
                {"id": "no", "type": "action", "title": "Fix it"}
            ]
        }
        path_taken = ["root", "no"]
        decisions = [
            {"node_id": "root", "answer": "No", "timestamp": datetime.now(timezone.utc).isoformat()},
            {"node_id": "no", "action_performed": "Restarted service", "timestamp": datetime.now(timezone.utc).isoformat()}
        ]

        result = convert_session_to_tree(path_taken, tree_snapshot, [], decisions)

        assert result["type"] == "decision"
        assert "Is it working?" in result["question"]
        assert len(result["children"]) == 1
        assert result["children"][0]["type"] == "action"

    def test_convert_with_custom_steps(self):
        """Test converting a session with custom steps."""
        tree_snapshot = {
            "id": "root",
            "type": "solution",
            "title": "Done"
        }
        custom_step_id = "custom-123"
        path_taken = ["root", custom_step_id]
        custom_steps = [
            {
                "id": custom_step_id,
                "type": "action",
                "content": "Custom troubleshooting step",
                "notes": "This worked!"
            }
        ]

        result = convert_session_to_tree(path_taken, tree_snapshot, custom_steps, [])

        assert result["type"] == "solution"
        assert len(result["children"]) == 1
        custom_node = result["children"][0]
        assert custom_node["type"] == "action"
        assert "Custom troubleshooting step" in custom_node["title"]

    def test_find_node_in_tree(self):
        """Test finding a node in nested tree structure."""
        tree = {
            "id": "root",
            "type": "decision",
            "children": [
                {
                    "id": "child1",
                    "type": "action",
                    "children": [
                        {"id": "grandchild", "type": "solution"}
                    ]
                },
                {"id": "child2", "type": "solution"}
            ]
        }

        # Find root
        assert _find_node_in_tree(tree, "root")["id"] == "root"

        # Find child
        assert _find_node_in_tree(tree, "child2")["type"] == "solution"

        # Find grandchild
        assert _find_node_in_tree(tree, "grandchild")["type"] == "solution"

        # Not found
        assert _find_node_in_tree(tree, "nonexistent") is None

    def test_generate_tree_name_basic(self):
        """Test generating tree name without ticket or client."""
        name = generate_tree_name_from_session("Network Troubleshooting")
        assert "Network Troubleshooting" in name
        assert "Session" in name

    def test_generate_tree_name_with_ticket(self):
        """Test generating tree name with ticket number."""
        name = generate_tree_name_from_session("VPN Issues", ticket_number="T-12345")
        assert "VPN Issues" in name
        assert "T-12345" in name

    def test_generate_tree_name_with_client(self):
        """Test generating tree name with client name."""
        name = generate_tree_name_from_session("Email Problems", client_name="Acme Corp")
        assert "Email Problems" in name
        assert "Acme Corp" in name

    def test_generate_tree_name_full(self):
        """Test generating tree name with all parameters."""
        name = generate_tree_name_from_session(
            "Server Down",
            ticket_number="INC-999",
            client_name="Tech Startup"
        )
        assert "Server Down" in name
        assert "INC-999" in name
        assert "Tech Startup" in name


class TestSaveSessionAsTreeAPI:
    """Test suite for save session as tree API endpoint."""

    async def test_save_session_as_tree_basic(self, client: AsyncClient, auth_headers, test_db, test_user):
        """Test basic save session as tree."""
        from uuid import UUID

        # Create a tree
        tree = Tree(
            name="Test Tree",
            description="Test",
            tree_structure={"id": "root", "type": "solution", "title": "Fix"},
            author_id=UUID(test_user["user_data"]["id"]),
            account_id=UUID(test_user["user_data"]["account_id"]),
            status='published'
        )
        test_db.add(tree)
        await test_db.commit()
        await test_db.refresh(tree)

        # Create a session
        session = Session(
            tree_id=tree.id,
            user_id=UUID(test_user["user_data"]["id"]),
            tree_snapshot=tree.tree_structure,
            path_taken=["root"],
            decisions=[{"node_id": "root", "timestamp": datetime.now(timezone.utc).isoformat()}],
            custom_steps=[]
        )
        test_db.add(session)
        await test_db.commit()
        await test_db.refresh(session)

        # Save as tree
        response = await client.post(
            f"/api/v1/sessions/{session.id}/save-as-tree",
            json={
                "tree_name": "Saved Session Tree",
                "description": "Saved from session",
                "status": "draft"
            },
            headers=auth_headers
        )

        assert response.status_code == 201
        data = response.json()
        assert "tree_id" in data
        assert data["tree_name"] == "Saved Session Tree"
        assert "draft" in data["message"]

    async def test_save_session_auto_generated_name(self, client: AsyncClient, auth_headers, test_db, test_user):
        """Test save session with auto-generated tree name."""
        from uuid import UUID

        tree = Tree(
            name="Original Tree",
            tree_structure={"id": "root", "type": "solution", "title": "Fix"},
            author_id=UUID(test_user["user_data"]["id"]),
            account_id=UUID(test_user["user_data"]["account_id"]),
            status='published'
        )
        test_db.add(tree)
        await test_db.commit()
        await test_db.refresh(tree)

        session = Session(
            tree_id=tree.id,
            user_id=UUID(test_user["user_data"]["id"]),
            tree_snapshot=tree.tree_structure,
            path_taken=["root"],
            decisions=[],
            custom_steps=[],
            ticket_number="T-123"
        )
        test_db.add(session)
        await test_db.commit()
        await test_db.refresh(session)

        response = await client.post(
            f"/api/v1/sessions/{session.id}/save-as-tree",
            json={"status": "draft"},
            headers=auth_headers
        )

        assert response.status_code == 201
        data = response.json()
        assert "Original Tree" in data["tree_name"]
        assert "T-123" in data["tree_name"]

    async def test_save_session_as_published_requires_validation(self, client: AsyncClient, auth_headers, test_db, test_user):
        """Test saving session as published tree validates structure."""
        from uuid import UUID

        # Create a simple tree with just a solution (will convert to valid linear tree)
        tree = Tree(
            name="Test Tree",
            tree_structure={"id": "root", "type": "solution", "title": "Fixed"},
            author_id=UUID(test_user["user_data"]["id"]),
            account_id=UUID(test_user["user_data"]["account_id"]),
            status='published'
        )
        test_db.add(tree)
        await test_db.commit()
        await test_db.refresh(tree)

        session = Session(
            tree_id=tree.id,
            user_id=UUID(test_user["user_data"]["id"]),
            tree_snapshot=tree.tree_structure,
            path_taken=["root"],
            decisions=[],
            custom_steps=[]
        )
        test_db.add(session)
        await test_db.commit()
        await test_db.refresh(session)

        # Try to save as published - should succeed with valid structure
        response = await client.post(
            f"/api/v1/sessions/{session.id}/save-as-tree",
            json={
                "tree_name": "Published Tree",
                "status": "published"
            },
            headers=auth_headers
        )

        # Should succeed since the converted tree structure is valid (solution node)
        assert response.status_code == 201

    async def test_save_session_links_to_original_tree(self, client: AsyncClient, auth_headers, test_db, test_user):
        """Test that saved tree is linked to original via fork relationship."""
        from uuid import UUID

        tree = Tree(
            name="Original Tree",
            tree_structure={"id": "root", "type": "solution", "title": "Fix"},
            author_id=UUID(test_user["user_data"]["id"]),
            account_id=UUID(test_user["user_data"]["account_id"]),
            status='published'
        )
        test_db.add(tree)
        await test_db.commit()
        await test_db.refresh(tree)

        session = Session(
            tree_id=tree.id,
            user_id=UUID(test_user["user_data"]["id"]),
            tree_snapshot=tree.tree_structure,
            path_taken=["root"],
            decisions=[],
            custom_steps=[]
        )
        test_db.add(session)
        await test_db.commit()
        await test_db.refresh(session)

        response = await client.post(
            f"/api/v1/sessions/{session.id}/save-as-tree",
            json={"tree_name": "Forked Tree", "status": "draft"},
            headers=auth_headers
        )

        assert response.status_code == 201
        data = response.json()

        # Verify the fork relationship by fetching the tree
        from sqlalchemy import select
        result = await test_db.execute(
            select(Tree).where(Tree.id == UUID(data["tree_id"]))
        )
        saved_tree = result.scalar_one()
        assert saved_tree.parent_tree_id == tree.id
        assert saved_tree.fork_depth == 1

    async def test_save_session_not_found(self, client: AsyncClient, auth_headers):
        """Test saving non-existent session returns 404."""
        from uuid import uuid4

        response = await client.post(
            f"/api/v1/sessions/{uuid4()}/save-as-tree",
            json={"status": "draft"},
            headers=auth_headers
        )

        assert response.status_code == 404

    async def test_save_other_user_session_forbidden(self, client: AsyncClient, test_db, test_user):
        """Test cannot save another user's session."""
        from uuid import UUID
        from app.models.user import User

        # Create a tree
        tree = Tree(
            name="Test Tree",
            tree_structure={"id": "root", "type": "solution", "title": "Fix"},
            author_id=UUID(test_user["user_data"]["id"]),
            account_id=UUID(test_user["user_data"]["account_id"]),
            status='published'
        )
        test_db.add(tree)
        await test_db.commit()
        await test_db.refresh(tree)

        # Create another user in same account
        other_user = User(
            email="other@example.com",
            password_hash="hashed",
            name="Other User",
            is_active=True,
            account_id=UUID(test_user["user_data"]["account_id"]),
            account_role="engineer"
        )
        test_db.add(other_user)
        await test_db.commit()
        await test_db.refresh(other_user)

        # Create session for the other user
        session = Session(
            tree_id=tree.id,
            user_id=other_user.id,
            tree_snapshot=tree.tree_structure,
            path_taken=["root"],
            decisions=[],
            custom_steps=[]
        )
        test_db.add(session)
        await test_db.commit()
        await test_db.refresh(session)

        # Try to save the session as test_user (should fail - filtered by user_id)
        from httpx import AsyncClient, ASGITransport
        async with AsyncClient(transport=ASGITransport(app=client._transport.app), base_url="http://test") as test_client:  # type: ignore
            # Login as test_user
            login_response = await test_client.post(
                "/api/v1/auth/login",
                data={"username": test_user["email"], "password": test_user["password"]}
            )
            token = login_response.json()["access_token"]

            response = await test_client.post(
                f"/api/v1/sessions/{session.id}/save-as-tree",
                json={"status": "draft"},
                headers={"Authorization": f"Bearer {token}"}
            )

            assert response.status_code == 404  # Session not found (filtered by user_id)
