from __future__ import annotations

import sys
from pathlib import Path
from typing import Any

import pytest

import polars as pl
from polars.exceptions import ComputeError
from polars.plugins import (
    _is_dynamic_lib,
    _resolve_plugin_path,
    _serialize_kwargs,
    register_plugin_function,
)


@pytest.mark.write_disk
def test_register_plugin_function_invalid_plugin_path(tmp_path: Path) -> None:
    tmp_path.mkdir(exist_ok=True)
    plugin_path = tmp_path / "lib.so"
    plugin_path.touch()

    expr = register_plugin_function(
        plugin_path=plugin_path, function_name="hello", args=5
    )

    with pytest.raises(ComputeError, match="error loading dynamic library"):
        pl.select(expr)


@pytest.mark.parametrize(
    ("input", "expected"),
    [
        (None, b""),
        ({}, b""),
        (
            {"hi": 0},
            b"\x80\x05\x95\x0b\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x02hi\x94K\x00s.",
        ),
    ],
)
def test_serialize_kwargs(input: dict[str, Any] | None, expected: bytes) -> None:
    assert _serialize_kwargs(input) == expected


@pytest.mark.write_disk
@pytest.mark.parametrize("use_abs_path", [True, False])
def test_resolve_plugin_path(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
    use_abs_path: bool,
) -> None:
    tmp_path.mkdir(exist_ok=True)

    mock_venv = tmp_path / ".venv"
    mock_venv.mkdir(exist_ok=True)
    mock_venv_lib = mock_venv / "lib"
    mock_venv_lib.mkdir(exist_ok=True)
    (mock_venv_lib / "lib1.so").touch()
    (mock_venv_lib / "__init__.py").touch()

    with pytest.MonkeyPatch.context() as mp:
        mp.setattr(sys, "prefix", str(mock_venv))
        expected_full_path = mock_venv_lib / "lib1.so"
        expected_relative_path = expected_full_path.relative_to(mock_venv)

        if use_abs_path:
            result = _resolve_plugin_path(mock_venv_lib, use_abs_path=use_abs_path)
            assert result == expected_full_path
        else:
            result = _resolve_plugin_path(mock_venv_lib, use_abs_path=use_abs_path)
            assert result == expected_relative_path


@pytest.mark.write_disk
def test_resolve_plugin_path_raises(tmp_path: Path) -> None:
    tmp_path.mkdir(exist_ok=True)
    (tmp_path / "__init__.py").touch()

    with pytest.raises(FileNotFoundError, match="no dynamic library found"):
        _resolve_plugin_path(tmp_path)


@pytest.mark.write_disk
@pytest.mark.parametrize(
    ("path", "expected"),
    [
        (Path("lib.so"), True),
        (Path("lib.pyd"), True),
        (Path("lib.dll"), True),
        (Path("lib.py"), False),
    ],
)
def test_is_dynamic_lib(path: Path, expected: bool, tmp_path: Path) -> None:
    tmp_path.mkdir(exist_ok=True)
    full_path = tmp_path / path
    full_path.touch()
    assert _is_dynamic_lib(full_path) is expected


@pytest.mark.write_disk
def test_is_dynamic_lib_dir(tmp_path: Path) -> None:
    path = Path("lib.so")
    full_path = tmp_path / path

    full_path.mkdir(exist_ok=True)
    (full_path / "hello.txt").touch()

    assert _is_dynamic_lib(full_path) is False
