import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from starlette.testclient import TestClient

from src.address.models import Zone, City
from src.database import Base, get_db
from src.main import app

DATABASE_URL = "sqlite+aiosqlite:///:memory:"

engine = create_async_engine(DATABASE_URL, future=True, echo=False)
AsyncSessionLocal = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)


@pytest_asyncio.fixture(scope="session", autouse=True)
async def prepare_database():
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)


@pytest_asyncio.fixture()
async def db_session():
    async with AsyncSessionLocal() as session:
        yield session
        await session.rollback()


@pytest.fixture()
def client(db_session):
    def override_get_db():
        yield db_session

    app.dependency_overrides[get_db] = override_get_db
    with TestClient(app) as client:
        yield client
    app.dependency_overrides.clear()


def test_create_zone_success(client):
    payload = {"name": "Test Zone"}
    response = client.post("/zone", json=payload)
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["name"] == "Test Zone"


@pytest.mark.asyncio
async def test_create_zone_duplicate_name(client, db_session):
    zone = Zone(name="Duplicate Zone")
    db_session.add(zone)
    await db_session.commit()

    payload = {"name": "Duplicate Zone"}
    response = client.post("/zone", json=payload)
    data = response.json()
    assert response.status_code == 400 or data.get("status") is False


@pytest.mark.asyncio
async def test_list_zones(client, db_session):
    for i in range(3):
        zone = Zone(name=f"Zone {i}")
        db_session.add(zone)
    await db_session.commit()

    response = client.get("/zone?limit=2&page=1")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert isinstance(data["data"], list)
    assert len(data["data"]) <= 2


@pytest.mark.asyncio
async def test_get_zone_by_id_success(client, db_session):
    zone = Zone(name="Zone for get_by_id")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    response = client.get(f"/zone/{zone.zone_id}")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["name"] == zone.name


def test_get_zone_by_id_not_found(client):
    response = client.get("/zone/9999999")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is False


@pytest.mark.asyncio
async def test_patch_zone_success(client, db_session):
    zone = Zone(name="Zone Before Patch")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    payload = {"name": "Zone After Patch"}
    response = client.patch(f"/zone/{zone.zone_id}", json=payload)
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    assert data["data"]["name"] == "Zone After Patch"


@pytest.mark.asyncio
async def test_delete_zone_success(client, db_session):
    zone = Zone(name="Zone To Delete")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    response = client.delete(f"/zone?zone_id={zone.zone_id}")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    assert data["data"]["zone_id"] == zone.zone_id


@pytest.mark.asyncio
async def test_delete_zone_with_linked_cities_fails(client, db_session):
    zone = Zone(name="Zone With Cities")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    city = City(name="City 1", zone_id=zone.zone_id)
    db_session.add(city)
    await db_session.commit()

    response = client.delete(f"/zone?zone_id={zone.zone_id}")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is False
    assert "linked cities" in data["message"].lower()


@pytest.mark.asyncio
async def test_get_zone_cities_list_empty(client, db_session):
    zone = Zone(name="Zone 1")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    response = client.get(f"/zone/{zone.zone_id}/city")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    assert isinstance(data["data"], list)
    assert len(data["data"]) == 0


@pytest.mark.asyncio
async def test_create_and_get_city(client, db_session):
    zone = Zone(name="Zone for city")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    city_payload = {"name": "City A", "zone_id": zone.zone_id}
    response = client.post("/zone/city", json=city_payload)
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    city_id = data["data"]["city_id"]

    response = client.get(f"/zone/{zone.zone_id}/city/{city_id}")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    assert data["data"]["name"] == "City A"


@pytest.mark.asyncio
async def test_update_city(client, db_session):
    zone = Zone(name="Zone for update")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    city = City(name="Old City", zone_id=zone.zone_id)
    db_session.add(city)
    await db_session.commit()
    await db_session.refresh(city)

    update_payload = {"name": "Updated City"}
    response = client.patch(f"/zone/{zone.zone_id}/city/{city.city_id}", json=update_payload)
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    assert data["data"]["name"] == "Updated City"


@pytest.mark.asyncio
async def test_delete_city(client, db_session):
    zone = Zone(name="Zone for delete")
    db_session.add(zone)
    await db_session.commit()
    await db_session.refresh(zone)

    city = City(name="City to delete", zone_id=zone.zone_id)
    db_session.add(city)
    await db_session.commit()
    await db_session.refresh(city)

    response = client.delete(f"/zone/zone/{zone.zone_id}/city/{city.city_id}")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is True
    assert "deleted" in data.get("message", "").lower()


@pytest.mark.asyncio
async def test_get_city_not_found(client):
    response = client.get("/zone/999999/city/999999")
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is False


@pytest.mark.asyncio
async def test_create_city_with_invalid_zone(client):
    city_payload = {"name": "Invalid City", "zone_id": 999999}
    response = client.post("/zone/city", json=city_payload)
    data = response.json()
    assert response.status_code == 200
    assert data["status"] is False
    assert "zone not found" in data["message"].lower()
