import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from starlette.testclient import TestClient
from sqlalchemy import select
from src.address.models import Zone, City
from src.database import get_db, Base
from src.main import app
from src.store.models import StoreAddress, Store, StorePhoto

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):
    # Перекриваємо залежність get_db на синхронний клієнт
    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()

@pytest.mark.asyncio
async def test_create_store_success(client):
    payload = {
        "name": "Test Store",
        "seo_keyword": "test-seo",
        "meta_title": "Test Meta Title",
        "meta_description": "Test Meta Description",
        "meta_keyword": "test,store",
        # logo: upload file,
    }
    response = client.post(
        "/store",
        data=payload
    )
    assert response.status_code == 201
    data = response.json()
    assert data["status"] is True
    assert data["data"]["name"] == "Test Store"

@pytest.mark.asyncio
async def test_list_stores(client, db_session):
    # Створимо декілька магазинів для списку
    for i in range(5):
        store = Store(name=f"Store {i}")
        db_session.add(store)
    await db_session.commit()

    response = client.get("/store?limit=3&page=1&sort[field]=name&sort[order]=asc")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert "items" in data["data"]
    assert len(data["data"]["items"]) <= 3

@pytest.mark.asyncio
async def test_get_store_by_id_success(client, db_session):
    store = Store(name="Store for get_by_id")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    response = client.get(f"/store/{store.store_id}")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["name"] == store.name

@pytest.mark.asyncio
async def test_get_store_by_id_not_found(client):
    response = client.get("/store/99999999")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is False

@pytest.mark.asyncio
async def test_update_store_success(client, db_session):
    store = Store(name="Store Before Update")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    payload = {
        "name": "Store After Update",
        "seo_keyword": "updated-seo",
        "meta_title": "Updated Meta",
        "meta_description": "Updated Desc",
        "meta_keyword": "updated,store"
        # logo пропускаємо для простоти
    }

    response = client.patch(f"/store/{store.store_id}", data=payload)
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["name"] == "Store After Update"

@pytest.mark.asyncio
async def test_delete_store_success(client, db_session):
    store = Store(name="Store To Delete")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    response = client.delete(f"/store/{store.store_id}")
    # Видалення повертає 204 No Content
    assert response.status_code == 204

    # Перевіримо, що магазин видалено з бази
    result = await db_session.execute(
        select(Store).where(Store.store_id == store.store_id)
    )
    deleted_store = result.scalar_one_or_none()
    assert deleted_store is None

@pytest.mark.asyncio
async def test_get_store_photos_empty(client, db_session):
    store = Store(name="Store with no photos")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    response = client.get(f"/store/{store.store_id}/photo")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert isinstance(data["data"], list)
    assert len(data["data"]) == 0

@pytest.mark.asyncio
async def test_add_and_get_store_photo(client, db_session):
    store = Store(name="Store with photo")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    file_content = b"fake image content"
    files = {"file": ("photo.jpg", file_content, "image/jpeg")}

    response = client.post(f"/store/{store.store_id}/photo", files=files)
    assert response.status_code == 201
    data = response.json()
    assert data["status"] is True
    photo_id = data["data"]["store_photo_id"]

    response = client.get(f"/store/{store.store_id}/photo")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert any(photo["store_photo_id"] == photo_id for photo in data["data"])

@pytest.mark.asyncio
async def test_delete_store_photo_success(client, db_session):
    store = Store(name="Store for photo delete")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    photo = StorePhoto(store_id=store.store_id, image="/fake/image.jpg")
    db_session.add(photo)
    await db_session.commit()
    await db_session.refresh(photo)

    response = client.delete(f"/store/{store.store_id}/photo/{photo.store_photo_id}")
    assert response.status_code == 204

    result = await db_session.execute(
        select(StorePhoto).where(StorePhoto.store_photo_id == photo.store_photo_id)
    )
    deleted_photo = result.scalar_one_or_none()
    assert deleted_photo is None

@pytest.mark.asyncio
async def test_add_store_address_success(client, db_session):
    zone = Zone(name="Zone for store address")
    city = City(name="City for store address", zone=zone)
    db_session.add_all([zone, city])
    await db_session.commit()
    await db_session.refresh(zone)
    await db_session.refresh(city)

    store = Store(name="Store with address")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    payload = {
        "address": "123 Test Street",
        "city_id": city.city_id,
        "zone_id": zone.zone_id,
        "phone_number": "1234567890",
        "schedule": "9-18",
    }
    response = client.post(f"/store/{store.store_id}/address", data=payload)
    assert response.status_code == 201
    data = response.json()
    assert data["status"] is True
    assert data["data"]["address"] == "123 Test Street"

@pytest.mark.asyncio
async def test_update_store_address_success(client, db_session):
    zone = Zone(name="Zone update addr")
    city = City(name="City update addr", zone=zone)
    db_session.add_all([zone, city])
    await db_session.commit()
    await db_session.refresh(zone)
    await db_session.refresh(city)

    store = Store(name="Store update addr")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    address = StoreAddress(
        store_id=store.store_id,
        address="Old Address",
        city_id=city.city_id,
        zone_id=zone.zone_id
    )
    db_session.add(address)
    await db_session.commit()
    await db_session.refresh(address)

    update_payload = {"address": "New Address", "phone_number": "111222333"}
    response = client.patch(f"/store/{store.store_id}/address/{address.store_address_id}", json=update_payload)
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["address"] == "New Address"
    assert data["data"]["phone_number"] == "111222333"

@pytest.mark.asyncio
async def test_get_store_addresses(client, db_session):
    zone = Zone(name="Zone for addr list")
    city = City(name="City for addr list", zone=zone)
    db_session.add_all([zone, city])
    await db_session.commit()
    await db_session.refresh(zone)
    await db_session.refresh(city)

    store = Store(name="Store for addr list")
    db_session.add(store)
    await db_session.commit()
    await db_session.refresh(store)

    for i in range(5):
        addr = StoreAddress(
            store_id=store.store_id,
            address=f"Address {i}",
            city_id=city.city_id,
            zone_id=zone.zone_id
        )
        db_session.add(addr)
    await db_session.commit()

    response = client.get(f"/store/{store.store_id}/address?limit=3&page=1")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert isinstance(data["data"], list)
    assert len(data["data"]) <= 3

@pytest.mark.asyncio
async def test_delete_store_not_found(client):
    response = client.delete("/store/9999999")
    assert response.status_code == 404 or response.status_code == 200
    data = response.json()
    if response.status_code == 200:
        assert data["status"] is False

@pytest.mark.asyncio
async def test_add_store_address_invalid_store(client, db_session):
    zone = Zone(name="Zone invalid store")
    city = City(name="City invalid store", zone=zone)
    await db_session.commit()

    payload = {
        "address": "No store address",
        "city_id": 999999,
        "zone_id": 999999,
    }
    response = client.post("/store/9999999/address", data=payload)
    assert response.status_code == 404