import getpass
import os
import platform
from pathlib import Path
from unittest import mock
from unittest.mock import mock_open

import pytest
from click.testing import CliRunner

from exponent.cli import login
from exponent.core.remote_execution import files, git
from exponent.core.remote_execution.client import RemoteExecutionClient
from exponent.core.remote_execution.languages.python import Kernel, get_python_env_info
from exponent.core.remote_execution.system_context import get_system_context
from exponent.core.remote_execution.types import (
    CodeExecutionRequest,
    CodeExecutionResponse,
    GetAllTrackedFilesRequest,
    GetAllTrackedFilesResponse,
    GetFileContentsRequest,
    GetFileContentsResponse,
    GetMatchingFilesRequest,
    GetMatchingFilesResponse,
    GitInfo,
    ListFilesRequest,
    PythonEnvInfo,
    SystemContextRequest,
    SystemContextResponse,
    SystemInfo,
)


async def test_exponent_login(cli_runner: CliRunner) -> None:
    with mock.patch("builtins.open", new_callable=mock_open):
        result = cli_runner.invoke(
            login, ["--key", "123456"], env={"EXPONENT_API_KEY": "123456"}
        )
        assert result.exit_code == 0
        assert "Saving API Key" in result.output


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_list_files(
    get_execution_requests: mock.Mock,
    default_temporary_directory: str,
) -> None:
    get_execution_requests.return_value = [
        ListFilesRequest(correlation_id="123456", directory=default_temporary_directory)
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=default_temporary_directory,
    ) as client:
        requests = await client.get_execution_requests(chat_uuid="123456")

        assert len(requests) == 1
        request = requests[0]
        assert isinstance(request, ListFilesRequest)
        assert request == ListFilesRequest(
            correlation_id="123456", directory=default_temporary_directory
        )

        response = files.list_files(request)
        assert response.correlation_id == "123456"
        assert sorted(response.filenames) == [
            ".git",
            "exponent.txt",
            "test1.py",
            "test2.py",
        ]


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_get_system_context(
    get_execution_requests: mock.Mock,
    default_temporary_directory: str,
    python_env_info: PythonEnvInfo,
) -> None:
    get_execution_requests.return_value = [
        SystemContextRequest(correlation_id="123456")
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=default_temporary_directory,
    ) as client:
        requests = await client.get_execution_requests(chat_uuid="123456")
        assert len(requests) == 1

        request = requests[0]
        assert isinstance(request, SystemContextRequest)
        assert request == SystemContextRequest(
            correlation_id="123456",
        )

        response = get_system_context(request, client.working_directory)
        assert response == SystemContextResponse(
            correlation_id="123456",
            exponent_txt="Hello, world!",
            system_info=SystemInfo(
                name=getpass.getuser(),
                cwd=default_temporary_directory,
                shell=os.environ.get("SHELL", "bash"),
                os=platform.system(),
                git=GitInfo(branch="master", remote=None),
                python_env=python_env_info,
            ),
        )


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_get_file_contents(
    get_execution_requests: mock.Mock,
    default_temporary_directory: str,
) -> None:
    get_execution_requests.return_value = [
        GetFileContentsRequest(correlation_id="123456", file_path="test1.py")
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=default_temporary_directory,
    ) as client:
        requests = await client.get_execution_requests(chat_uuid="123456")
        assert len(requests) == 1

        request = requests[0]
        assert isinstance(request, GetFileContentsRequest)
        assert request == GetFileContentsRequest(
            correlation_id="123456", file_path="test1.py"
        )

        response = files.get_file_contents(request, client.working_directory)
        assert response == GetFileContentsResponse(
            correlation_id="123456",
            file_path="test1.py",
            content="print('Hello, world!')",
        )


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_get_matching_files(
    get_execution_requests: mock.Mock,
    default_temporary_directory: str,
) -> None:
    get_execution_requests.return_value = [
        GetMatchingFilesRequest(correlation_id="123456", search_term="tes"),
        GetMatchingFilesRequest(correlation_id="123456", search_term="test1"),
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=default_temporary_directory,
    ) as client:
        EXPECTED_REQUESTS_COUNT = 2
        requests = await client.get_execution_requests(chat_uuid="123456")
        assert len(requests) == EXPECTED_REQUESTS_COUNT

        request1 = requests[0]
        assert isinstance(request1, GetMatchingFilesRequest)
        assert request1 == GetMatchingFilesRequest(
            correlation_id="123456", search_term="tes"
        )
        response1 = files.get_matching_files(request1, client.file_cache)
        assert response1 == GetMatchingFilesResponse(
            correlation_id="123456", file_paths=["test1.py", "test2.py", "exponent.txt"]
        )

        request2 = requests[1]
        assert isinstance(request2, GetMatchingFilesRequest)
        assert request2 == GetMatchingFilesRequest(
            correlation_id="123456", search_term="test1"
        )
        response2 = files.get_matching_files(request2, client.file_cache)
        assert response2 == GetMatchingFilesResponse(
            correlation_id="123456", file_paths=["test1.py", "test2.py", "exponent.txt"]
        )


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_get_matching_files_nested(
    get_execution_requests: mock.Mock,
    temporary_directory: str,
) -> None:
    # Setup test directory to look like this:
    # /folder
    #   /subfolder
    #     test1.py
    #     test2.py
    # /main
    #   core.java
    # random.tsx
    # .env
    # .gitignore (contains .env ignored)

    # Test queries for:
    # .env
    # random
    # test1
    # test2
    # .tsx
    # mjava
    import os

    # Create the necessary files and folders in the temporary directory
    os.makedirs(os.path.join(temporary_directory, "folder", "subfolder"))
    os.makedirs(os.path.join(temporary_directory, "main"))

    with open(
        os.path.join(temporary_directory, "folder", "subfolder", "test1.py"),
        "w",
    ) as f:
        f.write("# test1.py")

    with open(
        os.path.join(temporary_directory, "folder", "subfolder", "test2.py"),
        "w",
    ) as f:
        f.write("# test2.py")

    with open(os.path.join(temporary_directory, "main", "core.java"), "w") as f:
        f.write("// core.java")

    with open(os.path.join(temporary_directory, "random.tsx"), "w") as f:
        f.write("// random.tsx")

    with open(os.path.join(temporary_directory, ".env"), "w") as f:
        f.write("SECRET_KEY=123456")

    with open(os.path.join(temporary_directory, ".gitignore"), "w") as f:
        f.write(".env")

    # Test queries for various search terms
    test_cases = [
        # ("tes", ["folder/subfolder/test1.py", "folder/subfolder/test2.py"]),
        ("random", ["random.tsx"]),
        (".env", []),
        ("core", ["main/core.java"]),
        (".tsx", ["random.tsx"]),
        ("java", ["main/core.java"]),
        ("nonexistent", []),
        ("folder", ["folder/subfolder/test1.py", "folder/subfolder/test2.py"]),
        ("subfolder", ["folder/subfolder/test1.py", "folder/subfolder/test2.py"]),
        ("test1", ["folder/subfolder/test1.py"]),
        ("test2", ["folder/subfolder/test2.py"]),
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=temporary_directory,
    ) as client:
        # TODO: Bring back assertion that match on the matched files
        for search_term, _ in test_cases:
            get_execution_requests.return_value = [
                GetMatchingFilesRequest(
                    correlation_id="123456", search_term=search_term
                )
            ]
            requests = await client.get_execution_requests(chat_uuid="123456")
            assert len(requests) == 1

            request = requests[0]
            assert isinstance(request, GetMatchingFilesRequest)
            assert request == GetMatchingFilesRequest(
                correlation_id="123456", search_term=search_term
            )

            response = files.get_matching_files(request, client.file_cache)
            assert isinstance(response, GetMatchingFilesResponse)
            assert response.correlation_id == "123456"


@pytest.mark.skip
async def test_python_kernel() -> None:
    kernel = Kernel()
    result = await kernel.execute_code("print('Hello, world!')")
    assert result == "Hello, world!\n"


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_execute_code_diff(
    get_execution_requests: mock.Mock,
    tmpdir: Path,
) -> None:
    os.chdir(tmpdir)

    get_execution_requests.return_value = [
        CodeExecutionRequest(
            correlation_id="123456",
            language="diff",
            content='filepath=echo_server.py\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\nclass Message(BaseModel):\n    content: str\n\n@app.get("/echo")\ndef echo(message: Message):\n    return {"Echo": message.content}\n\nif __name__ == "__main__":\n    import uvicorn\n    uvicorn.run(app, host="127.0.0.1", port=8000)\n',
        ),
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=str(tmpdir),
    ) as client:
        requests = await client.get_execution_requests(chat_uuid="123456")
        assert len(requests) == 1

        request = requests[0]
        assert isinstance(request, CodeExecutionRequest)

        response = await client.handle_request(request)
        assert response == CodeExecutionResponse(
            correlation_id="123456", content="Created file echo_server.py"
        )

        with open("echo_server.py") as f:
            contents = f.read()
        assert (
            contents
            == 'from fastapi import FastAPI\nfrom pydantic import BaseModel\n\napp = FastAPI()\n\nclass Message(BaseModel):\n    content: str\n\n@app.get("/echo")\ndef echo(message: Message):\n    return {"Echo": message.content}\n\nif __name__ == "__main__":\n    import uvicorn\n    uvicorn.run(app, host="127.0.0.1", port=8000)\n'
        )


@mock.patch(
    "exponent.core.remote_execution.client.RemoteExecutionClient.get_execution_requests"
)
async def test_get_all_tracked_files(
    get_execution_requests: mock.Mock,
    default_temporary_directory: str,
) -> None:
    get_execution_requests.return_value = [
        GetAllTrackedFilesRequest(correlation_id="123456"),
    ]

    async with RemoteExecutionClient.session(
        api_key="123456",
        base_url="https://example.com",
        working_directory=default_temporary_directory,
    ) as client:
        EXPECTED_REQUESTS_COUNT = 1
        requests = await client.get_execution_requests(chat_uuid="123456")
        assert len(requests) == EXPECTED_REQUESTS_COUNT

        request1 = requests[0]
        assert isinstance(request1, GetAllTrackedFilesRequest)
        assert request1 == GetAllTrackedFilesRequest(
            correlation_id="123456",
        )
        response1 = git.get_all_tracked_files(request1, client.working_directory)
        assert response1 == GetAllTrackedFilesResponse(
            correlation_id="123456",
            file_paths=[
                "exponent.txt",
                "test1.py",
                "test2.py",
            ],
        )


def test_get_python_env_info() -> None:
    venv_info = get_python_env_info()
    assert venv_info.interpreter_path
    assert venv_info.interpreter_version
    assert venv_info.interpreter_path.endswith("python")
    assert venv_info.interpreter_version == platform.python_version()
