import importlib
import sys

import pytest
import shapely.wkb
from shapely.geometry import Point

import geoalchemy2.shape
from geoalchemy2.elements import WKBElement
from geoalchemy2.elements import WKTElement
from geoalchemy2.shape import from_shape
from geoalchemy2.shape import to_shape


def test_import_without_shapely(monkeypatch):
    """Ensure geoalchemy2.shape can be imported when Shapely is not installed."""
    # Save the current shapely entries in sys.modules

    shapely_modules = {
        k: v for k, v in sys.modules.items() if k == "shapely" or k.startswith("shapely.")
    }
    CHECK_COMPLETE = 0
    with monkeypatch.context() as m:
        try:
            for k in shapely_modules:
                m.setitem(sys.modules, k, None)

            # Now import the module - must not raise ImportError

            importlib.reload(geoalchemy2.shape)

            assert not geoalchemy2.shape.HAS_SHAPELY
            with pytest.raises(
                ImportError,
                match="This feature needs the optional Shapely dependency",
            ):
                to_shape(WKTElement("SRID=3857;POINT(1 2)"))
            CHECK_COMPLETE += 1
        finally:
            # Restore shapely modules and reload to return to normal state

            for k, v in shapely_modules.items():
                sys.modules[k] = v

            importlib.reload(geoalchemy2.shape)
            to_shape(WKTElement("SRID=3857;POINT(1 2)"))  # Should not raise ImportError
            CHECK_COMPLETE += 1

    assert geoalchemy2.shape.HAS_SHAPELY
    assert CHECK_COMPLETE == 2


def test_check_shapely(monkeypatch):
    @geoalchemy2.shape.check_shapely()
    def f():
        return "ok"

    assert f() == "ok"

    with monkeypatch.context() as m:
        m.setattr(geoalchemy2.shape, "HAS_SHAPELY", False)
        with pytest.raises(ImportError):
            f()


def test_to_shape_WKBElement():
    # POINT(1 2)
    e = WKBElement(
        b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@"
    )
    s = to_shape(e)
    assert isinstance(s, Point)
    assert s.x == 1
    assert s.y == 2


def test_to_shape_WKBElement_str():
    # POINT(1 2)
    e = WKBElement("0101000000000000000000f03f0000000000000040")
    s = to_shape(e)
    assert isinstance(s, Point)
    assert s.x == 1
    assert s.y == 2


def test_to_shape_ExtendedWKBElement():
    # SRID=3857;POINT(1 2 3)
    e = WKBElement(
        b"\x01\x01\x00\x00\xa0\x11\x0f\x00\x00\x00"
        b"\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00"
        b"\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@",
        extended=True,
    )
    s = to_shape(e)
    assert isinstance(s, Point)
    assert s.x == 1
    assert s.y == 2
    assert s.z == 3


def test_to_shape_ExtendedWKTElement():
    e = WKTElement("SRID=3857;POINT(1 2)", extended=True)
    s = to_shape(e)
    assert isinstance(s, Point)
    assert s.x == 1
    assert s.y == 2


def test_to_shape_WKTElement():
    e = WKTElement("POINT(1 2)")
    s = to_shape(e)
    assert isinstance(s, Point)
    assert s.x == 1
    assert s.y == 2


def test_to_shape_wrong_type():
    with pytest.raises(TypeError, match="Only WKBElement and WKTElement objects are supported"):
        to_shape(0)


def test_from_shape():
    # Standard case: POINT(1 2)
    expected = WKBElement("0101000000000000000000f03f0000000000000040")
    p = Point(1, 2)
    e = from_shape(p)
    assert isinstance(e, WKBElement)
    assert isinstance(e.data, memoryview)
    assert e == expected

    s = shapely.wkb.loads(bytes(e.data))
    assert isinstance(s, Point)
    assert s.equals(p)

    # Standard case with SRID: SRID=2145;POINT(1 2)
    expected2 = WKBElement("0101000000000000000000f03f0000000000000040", srid=2154)
    p = Point(1, 2)
    e2 = from_shape(p, srid=2154)
    assert isinstance(e2, WKBElement)
    assert isinstance(e2.data, memoryview)
    assert e2 == expected2

    s2 = shapely.wkb.loads(bytes(e2.data))
    assert isinstance(s2, Point)
    assert s2.equals(p)

    # Extended case: SRID=2145;POINT(1 2)
    expected3 = WKBElement("01010000206a080000000000000000f03f0000000000000040", extended=True)
    e3 = from_shape(p, srid=2154, extended=True)
    assert isinstance(e3, WKBElement)
    assert isinstance(e3.data, memoryview)
    assert e3 == expected3

    s3 = shapely.wkb.loads(bytes(e3.data))
    assert isinstance(s, Point)
    assert s3.equals(p)
