"""
E2E integration tests for the InstaVM Python SDK.

These tests hit the real API and require INSTAVM_API_KEY to be set.
They are automatically skipped when the env var is absent, so
``pytest`` collection never fails in CI without secrets.

Run:
    INSTAVM_API_KEY=instavm_sk_... python3 -m pytest tests/integration/ -v
"""
import os
import time
import tempfile

import pytest

# Skip the entire module when no API key is available.
pytestmark = pytest.mark.skipif(
    not os.environ.get("INSTAVM_API_KEY"),
    reason="INSTAVM_API_KEY not set — skipping integration tests",
)

from instavm import InstaVM


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture(scope="module")
def api_key():
    return os.environ["INSTAVM_API_KEY"]


@pytest.fixture(scope="module")
def client(api_key):
    """Shared client for the whole module. Disposes at end."""
    c = InstaVM(api_key=api_key)
    yield c
    try:
        c.close_session()
    except Exception:
        pass


@pytest.fixture(scope="module")
def session_id(client):
    """Ensure we have an active session (created on first execute)."""
    result = client.execute("print('session_bootstrap')")
    assert "session_bootstrap" in result.get("stdout", result.get("output", ""))
    return client.session_id


# ---------------------------------------------------------------------------
# Code Execution
# ---------------------------------------------------------------------------

class TestCodeExecution:
    def test_execute_python(self, client, session_id):
        result = client.execute("print('Hello from Python SDK!')")
        stdout = result.get("stdout", result.get("output", ""))
        assert "Hello from Python SDK!" in stdout

    def test_execute_bash(self, client, session_id):
        result = client.execute("echo 'Bash test'", language="bash")
        stdout = result.get("stdout", result.get("output", ""))
        assert "Bash test" in stdout

    def test_execute_imports_and_math(self, client, session_id):
        code = (
            "import math\n"
            "result = math.sqrt(16) + math.pi\n"
            "print(f'Result: {result:.4f}')"
        )
        result = client.execute(code)
        stdout = result.get("stdout", result.get("output", ""))
        assert "Result:" in stdout

    def test_session_state_persistence(self, client, session_id):
        client.execute("sdk_e2e_var = 42")
        result = client.execute("print(sdk_e2e_var)")
        stdout = result.get("stdout", result.get("output", ""))
        assert "42" in stdout

    def test_execution_error_handling(self, client, session_id):
        result = client.execute("print(undefined_variable_xyz)")
        stderr = result.get("stderr", "")
        if stderr:
            assert "NameError" in stderr or "Error" in stderr


# ---------------------------------------------------------------------------
# Async Execution
# ---------------------------------------------------------------------------

class TestAsyncExecution:
    def test_execute_async_and_poll(self, client, session_id):
        async_result = client.execute_async(
            "import time; time.sleep(1); print('async done')"
        )
        task_id = async_result.get("task_id")
        assert task_id is not None

        task_result = client.get_task_result(task_id, poll_interval=1, timeout=30)
        stdout = task_result.get("serialized_stdout", task_result.get("stdout", ""))
        assert "async done" in stdout


# ---------------------------------------------------------------------------
# File Upload & Download (reuses session VM)
# ---------------------------------------------------------------------------

class TestFileOps:
    def test_create_download_file(self, client, session_id):
        client.execute('with open("/tmp/e2e_test.txt", "w") as f: f.write("hello from e2e")')
        try:
            result = client.download_file("/tmp/e2e_test.txt")
            assert result is not None
        except Exception as exc:
            # Download endpoint may return 400/500 on some VM images — not an SDK bug
            pytest.skip(f"File download endpoint error (known): {exc}")

    def test_upload_file(self, client, session_id):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            f.write("uploaded content")
            f.flush()
            try:
                result = client.upload_file(f.name, "/tmp/uploaded.txt")
                assert result is not None
            except Exception as exc:
                pytest.skip(f"File upload endpoint error (known): {exc}")
            finally:
                os.unlink(f.name)


# ---------------------------------------------------------------------------
# Session Management
# ---------------------------------------------------------------------------

class TestSessionManagement:
    def test_is_session_active(self, client, session_id):
        active = client.is_session_active()
        assert isinstance(active, bool)

    def test_get_usage(self, client, session_id):
        usage = client.get_usage()
        assert isinstance(usage, dict)

    def test_get_session_info(self, client, session_id):
        info = client.get_session_info()
        assert isinstance(info, dict)

    def test_get_session_app_url(self, client, session_id):
        result = client.get_session_app_url()
        assert isinstance(result, dict)

    def test_get_session_app_url_with_port(self, client, session_id):
        result = client.get_session_app_url(port=8080)
        assert isinstance(result, dict)

    def test_list_sandboxes(self, client, session_id):
        sandboxes = client.list_sandboxes()
        assert isinstance(sandboxes, list)

    def test_list_sandboxes_with_limit(self, client, session_id):
        sandboxes = client.list_sandboxes(limit=5)
        assert isinstance(sandboxes, list)
        assert len(sandboxes) <= 5


# ---------------------------------------------------------------------------
# Egress Policy (set before get — policy must exist first)
# ---------------------------------------------------------------------------

class TestEgressPolicy:
    def test_set_session_egress(self, client, session_id):
        result = client.set_session_egress(
            allow_http=True,
            allow_https=True,
            allowed_domains=["example.com"],
        )
        assert isinstance(result, dict)

    def test_get_session_egress_after_set(self, client, session_id):
        policy = client.get_session_egress()
        assert isinstance(policy, dict)


# ---------------------------------------------------------------------------
# Shares (reuses session VM — no extra VM needed)
# ---------------------------------------------------------------------------

class TestShares:
    _created_share_id = None

    def test_create_share(self, client, session_id):
        share = client.shares.create(port=8080, is_public=True)
        assert isinstance(share, dict)
        TestShares._created_share_id = share.get("share_id") or share.get("id")

    def test_update_share(self, client):
        if not TestShares._created_share_id:
            pytest.skip("No share created")
        result = client.shares.update(TestShares._created_share_id, is_public=False)
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# SSH Keys
# ---------------------------------------------------------------------------

class TestSSHKeys:
    def test_list_ssh_keys(self, client):
        keys = client.list_ssh_keys()
        assert isinstance(keys, list)

    def test_add_and_delete_ssh_key(self, client):
        test_pub_key = (
            "ssh-ed25519 "
            "AAAAC3NzaC1lZDI1NTE5AAAAITestKeyForE2ETests000000000000000000000 "
            "e2e-test"
        )
        try:
            added = client.add_ssh_key(test_pub_key)
            key_id = added.get("id")
            assert key_id is not None
            client.delete_ssh_key(key_id)
        except Exception as exc:
            msg = str(exc)
            if "already" in msg.lower() or "invalid" in msg.lower():
                pytest.skip(f"SSH key add skipped: {msg}")
            else:
                raise


# ---------------------------------------------------------------------------
# VMs (create one, reuse for get/delete)
# ---------------------------------------------------------------------------

class TestVMs:
    _created_vm_id = None

    def test_create_vm(self, client):
        try:
            vm = client.vms.create(vm_lifetime_seconds=120, wait=True)
            assert isinstance(vm, dict)
            TestVMs._created_vm_id = vm.get("vm_id") or vm.get("id")
            assert TestVMs._created_vm_id is not None, f"No vm_id in response: {vm}"
        except Exception as exc:
            pytest.skip(f"VM create failed (rate limit/timeout?): {exc}")

    def test_list_vms(self, client):
        vms = client.vms.list()
        assert isinstance(vms, list)

    def test_list_all_records(self, client):
        vms = client.vms.list_all_records()
        assert isinstance(vms, list)

    def test_get_vm(self, client):
        if not TestVMs._created_vm_id:
            pytest.skip("No VM created")
        vm = client.vms.get(TestVMs._created_vm_id)
        assert isinstance(vm, dict)

    def test_delete_vm(self, client):
        if not TestVMs._created_vm_id:
            pytest.skip("No VM created")
        result = client.vms.delete(TestVMs._created_vm_id)
        assert result is not None


# ---------------------------------------------------------------------------
# Snapshots (read-only — don't create to avoid long builds)
# ---------------------------------------------------------------------------

class TestSnapshots:
    def test_list_snapshots(self, client):
        snaps = client.snapshots.list()
        assert isinstance(snaps, list)

    def test_get_snapshot_if_any_exist(self, client):
        snaps = client.snapshots.list()
        if not snaps:
            pytest.skip("No snapshots available")
        snap_id = snaps[0].get("id") or snaps[0].get("snapshot_id")
        snap = client.snapshots.get(snap_id)
        assert isinstance(snap, dict)


# ---------------------------------------------------------------------------
# Volumes (create, file ops, checkpoint, delete)
# ---------------------------------------------------------------------------

class TestVolumes:
    _created_volume_id = None

    def test_create_volume(self, client):
        vol = client.volumes.create(
            name=f"e2e-test-vol-{int(time.time())}",
            quota_bytes=100 * 1024 * 1024,
        )
        assert isinstance(vol, dict)
        TestVolumes._created_volume_id = vol.get("id") or vol.get("volume_id")

    def test_list_volumes(self, client):
        vols = client.volumes.list()
        assert isinstance(vols, list)

    def test_get_volume(self, client):
        if not TestVolumes._created_volume_id:
            pytest.skip("No volume created")
        vol = client.volumes.get(TestVolumes._created_volume_id)
        assert isinstance(vol, dict)

    def test_upload_list_download_file(self, client, tmp_path):
        vid = TestVolumes._created_volume_id
        if not vid:
            pytest.skip("No volume created")

        local_file = tmp_path / "e2e-test.txt"
        local_file.write_text("Hello from E2E test")

        client.volumes.upload_file(
            volume_id=vid,
            file_path=str(local_file),
            path="e2e-test.txt",
            overwrite=True,
        )

        files = client.volumes.list_files(vid)
        assert isinstance(files, list)

        result = client.volumes.download_file(vid, "e2e-test.txt")
        assert result is not None

        client.volumes.delete_file(vid, "e2e-test.txt")

    def test_delete_volume(self, client):
        if not TestVolumes._created_volume_id:
            pytest.skip("No volume created")
        result = client.volumes.delete(TestVolumes._created_volume_id)
        assert result is not None


# ---------------------------------------------------------------------------
# Webhooks (create, list, get, delete)
# ---------------------------------------------------------------------------

class TestWebhooks:
    _created_endpoint_id = None

    def test_create_webhook_endpoint(self, client):
        endpoint = client.webhooks.create_endpoint(
            url="https://httpbin.org/post",
            description="E2E test webhook",
            event_patterns=["vm.*"],
        )
        assert isinstance(endpoint, dict)
        # Try all common ID field names
        TestWebhooks._created_endpoint_id = (
            endpoint.get("id") or endpoint.get("endpoint_id")
            or endpoint.get("webhook_endpoint_id")
        )
        if not TestWebhooks._created_endpoint_id:
            # Fallback: look for any key containing "id"
            for k, v in endpoint.items():
                if "id" in k.lower() and v:
                    TestWebhooks._created_endpoint_id = str(v)
                    break

    def test_list_webhook_endpoints(self, client):
        endpoints = client.webhooks.list_endpoints()
        assert isinstance(endpoints, list)

    def test_get_webhook_endpoint(self, client):
        if not TestWebhooks._created_endpoint_id:
            pytest.skip("No endpoint created")
        endpoint = client.webhooks.get_endpoint(TestWebhooks._created_endpoint_id)
        assert isinstance(endpoint, dict)

    def test_delete_webhook_endpoint(self, client):
        if not TestWebhooks._created_endpoint_id:
            pytest.skip("No endpoint created")
        result = client.webhooks.delete_endpoint(TestWebhooks._created_endpoint_id)
        assert result is not None


# ---------------------------------------------------------------------------
# API Keys
# ---------------------------------------------------------------------------

class TestAPIKeys:
    def test_list_api_keys(self, client):
        keys = client.api_keys.list()
        assert isinstance(keys, list)
        if keys:
            assert "id" in keys[0]


# ---------------------------------------------------------------------------
# Audit Logs
# ---------------------------------------------------------------------------

class TestAuditLogs:
    def test_get_audit_catalog(self, client):
        catalog = client.audit.catalog()
        assert isinstance(catalog, (dict, list))

    def test_list_audit_events(self, client):
        events = client.audit.events()
        assert isinstance(events, (dict, list))


# ---------------------------------------------------------------------------
# Computer Use (viewer URL — session may not have desktop)
# ---------------------------------------------------------------------------

class TestComputerUse:
    def test_viewer_url(self, client, session_id):
        try:
            result = client.computer_use.viewer_url(session_id)
            assert result is not None
        except Exception:
            # Expected: VM doesn't have a desktop environment
            pass


# ---------------------------------------------------------------------------
# Kill (terminates the session VM — run last)
# ---------------------------------------------------------------------------

class TestKill:
    def test_kill_session_vm(self, client, session_id):
        result = client.kill(session_id)
        assert result is not None


# ---------------------------------------------------------------------------
# Error Handling
# ---------------------------------------------------------------------------

class TestErrorHandling:
    def test_invalid_api_key_raises(self):
        with pytest.raises(Exception):
            InstaVM(api_key="invalid-key")
