import httpx
import pytest
from datetime import datetime, timedelta
from sqlalchemy import select
from src.review.models import StoreReview, ReviewStatusEnum, ProductReview, ServiceReview
from src.customer.models import Customer
from src.store.models import Store

import pytest
import pytest_asyncio
from datetime import datetime
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from starlette.testclient import TestClient

from src.database import Base, get_db
from src.customer.models import Customer
from src.store.models import Store
from src.manufacturer.models import Manufacturer
from src.review.models import StoreReview, ManufacturerReview, ReviewStatusEnum
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")
async def connection():
    async with engine.connect() as conn:
        await conn.run_sync(Base.metadata.create_all)
        yield conn
        await conn.run_sync(Base.metadata.drop_all)


@pytest_asyncio.fixture()
async def db_session(connection):
    async with connection.begin_nested():
        async with AsyncSession(bind=connection, expire_on_commit=False) as session:
            yield session
    # rollback nested транзакції відбудеться автоматично


@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 c:
        yield c
    app.dependency_overrides.clear()


@pytest_asyncio.fixture()
async def create_customer(db_session):
    async def _create_customer(
        name="Test User",
        phone=None,
        password="hashed",
        banned=False,
        auth_type="phone",
        comment=None
    ):
        # Генеруємо унікальний телефон, якщо не передано
        if phone is None:
            phone = f"3805011{int(datetime.utcnow().timestamp() * 1000000)}"

        customer = Customer(
            name=name,
            phone=phone,
            password=password,
            banned=banned,
            auth_type=auth_type,
            comment=comment,
        )
        db_session.add(customer)
        await db_session.flush()    # flush, не commit
        await db_session.refresh(customer)
        return customer
    return _create_customer


@pytest_asyncio.fixture()
async def create_store(db_session):
    async def _create_store(name="Test Store"):
        store = Store(name=name)
        db_session.add(store)
        await db_session.flush()
        await db_session.refresh(store)
        return store
    return _create_store


@pytest_asyncio.fixture()
async def create_manufacturer(db_session):
    async def _create_manufacturer(name="Test Manufacturer"):
        manufacturer = Manufacturer(name=name)
        db_session.add(manufacturer)
        await db_session.flush()
        await db_session.refresh(manufacturer)
        return manufacturer
    return _create_manufacturer


@pytest_asyncio.fixture()
async def create_store_review(db_session, create_store, create_customer):
    async def _create_store_review(
        text="Good store",
        rating=5,
        status=ReviewStatusEnum.new,
        ip="127.0.0.1",
        store=None,
        customer=None
    ):
        if store is None:
            store = await create_store()
        if customer is None:
            customer = await create_customer()
        review = StoreReview(
            text=text,
            rating=rating,
            status=status,
            ip=ip,
            store=store,
            customer=customer,
            date_added=datetime.utcnow(),
            date_modify=datetime.utcnow(),
        )
        db_session.add(review)
        await db_session.flush()
        await db_session.refresh(review)
        return review
    return _create_store_review


@pytest_asyncio.fixture()
async def create_manufacturer_review(db_session, create_manufacturer, create_customer):
    async def _create_manufacturer_review(
        text="Good manufacturer",
        rating=4,
        status=ReviewStatusEnum.new,
        ip="127.0.0.1",
        manufacturer=None,
        customer=None
    ):
        if manufacturer is None:
            manufacturer = await create_manufacturer()
        if customer is None:
            customer = await create_customer()
        review = ManufacturerReview(
            text=text,
            rating=rating,
            status=status,
            ip=ip,
            manufacturer=manufacturer,
            customer=customer,
            date_added=datetime.utcnow(),
            date_modify=datetime.utcnow(),
        )
        db_session.add(review)
        await db_session.flush()
        await db_session.refresh(review)
        return review
    return _create_manufacturer_review


@pytest_asyncio.fixture
async def create_product_review(db_session, create_customer, create_manufacturer):
    async def _create_product_review(
            text="Sample product review",
            rating=5,
            status=ReviewStatusEnum.new,
            ip="127.0.0.1",
            customer=None,
            manufacturer=None,
    ):
        if customer is None:
            customer = await create_customer()
        if manufacturer is None:
            manufacturer = await create_manufacturer()

        review = ProductReview(
            text=text,
            rating=rating,
            status=status,
            ip=ip,
            customer_id=customer.customer_id,
            manufacturer_id=manufacturer.manufacturer_id,
            date_added=datetime.utcnow(),
            date_modify=datetime.utcnow(),
        )
        db_session.add(review)
        await db_session.flush()
        await db_session.refresh(review)
        return review

    return _create_product_review


@pytest_asyncio.fixture()
async def create_service_review(db_session, create_customer):
    async def _create_service_review(text="Sample service text", rating=4, status=ReviewStatusEnum.new):
        customer = await create_customer()
        review = ServiceReview(
            text=text,
            rating=rating,
            status=status,
            ip="127.0.0.1",
            customer_id=customer.customer_id,
        )
        db_session.add(review)
        await db_session.flush()
        await db_session.refresh(review)
        return review
    return _create_service_review


@pytest.mark.asyncio
async def test_get_store_reviews_basic(client, db_session):
    # Додамо кілька записів у БД
    store = Store(name="Test Store")
    customer = Customer(name="Alice", phone="380501112233", password="hashed", banned=False, auth_type="phone")
    review = StoreReview(
        store=store,
        customer=customer,
        text="Great store!",
        rating=5,
        status=ReviewStatusEnum.approved,
        ip="127.0.0.1",
    )
    db_session.add_all([store, customer, review])
    await db_session.commit()

    response = client.get("/review/store")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["total"] >= 1
    assert any(r["text"] == "Great store!" for r in data["data"])

@pytest.mark.asyncio
async def test_patch_store_review_status_success(client, db_session):
    store = Store(name="Patch Store")
    customer = Customer(name="Bob", phone="380501112244", password="hashed", banned=False, auth_type="phone")
    review = StoreReview(
        store=store,
        customer=customer,
        text="Needs improvement",
        rating=3,
        status=ReviewStatusEnum.new,
        ip="127.0.0.1",
    )
    db_session.add_all([store, customer, review])
    await db_session.commit()
    await db_session.refresh(review)

    new_status = 1  # approved
    response = client.patch(f"/review/store/{review.store_review_id}?status={new_status}")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["status"] == new_status

@pytest.mark.asyncio
async def test_patch_store_review_status_invalid_status(client):
    response = client.patch("/review/store/1?status=5")
    assert response.status_code == 400
    data = response.json()
    assert "Invalid status value" in data["detail"]

@pytest.mark.asyncio
async def test_patch_store_review_status_not_found(client):
    response = client.patch("/review/store/999999?status=1")
    assert response.status_code == 404
    data = response.json()
    assert "not found" in data["detail"].lower()


@pytest.mark.asyncio
async def test_get_product_reviews(async_client, create_product_review):
    review = await create_product_review(text="Awesome product", rating=5)
    response = await async_client.get("/review/product")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["total"] >= 1
    assert any(r["product_review_id"] == review.product_review_id for r in data["data"])


@pytest.mark.asyncio
async def test_get_product_reviews_with_filters(client, create_product_review):
    review = await create_product_review(text="Filtered product", rating=3, status=ReviewStatusEnum.approved)
    response = client.get("/review", params={"status": 1})  # <-- Ось тут зміна
    assert response.status_code == 200
    data = response.json()
    assert all(r["status"] == 1 for r in data["data"])
    assert any(r["product_review_id"] == review.product_review_id for r in data["data"])

@pytest.mark.asyncio
async def test_get_product_reviews(client, create_product_review):
    review = await create_product_review(text="Awesome product", rating=5)
    response = client.get("/review/product")  # <-- Зверни увагу: саме "/review"
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["total"] >= 1
    assert any(r["product_review_id"] == review.product_review_id for r in data["data"])

@pytest.mark.asyncio
async def test_get_product_reviews_with_filters(client, create_product_review):
    review = await create_product_review(text="Filtered product", rating=3, status=ReviewStatusEnum.approved)
    response = client.get("/review/product", params={"status": 1})
    assert response.status_code == 200
    data = response.json()
    assert all(r["status"] == 1 for r in data["data"])
    assert any(r["product_review_id"] == review.product_review_id for r in data["data"])

@pytest.mark.asyncio
async def test_patch_product_review_status_success(client, create_product_review):
    review = await create_product_review()
    response = client.patch(f"/review/product/{review.product_review_id}", params={"status": 2})
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["status"] == 2

@pytest.mark.asyncio
async def test_patch_product_review_status_invalid_status(client, create_product_review):
    review = await create_product_review()
    response = client.patch(f"/review/product/{review.product_review_id}", params={"status": 99})
    assert response.status_code == 400
    data = response.json()
    assert "Invalid status value" in data["detail"]

@pytest.mark.asyncio
async def test_patch_product_review_status_not_found(client):
    response = client.patch("/review/product/999999", params={"status": 1})
    assert response.status_code == 404
    data = response.json()
    assert "not found" in data["detail"].lower()

@pytest.mark.asyncio
async def test_get_service_reviews(client, create_service_review):
    review = await create_service_review(text="Great service", rating=5)
    response = client.get("/review/service")
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["total"] >= 1
    assert any(r["service_review_id"] == review.service_review_id for r in data["data"])

@pytest.mark.asyncio
async def test_get_service_reviews_with_filters(client, create_service_review):
    review = await create_service_review(text="Filtered service", rating=2, status=ReviewStatusEnum.approved)
    response = client.get("/review/service", params={"status": 1})
    assert response.status_code == 200
    data = response.json()
    assert all(r["status"] == 1 for r in data["data"])
    assert any(r["service_review_id"] == review.service_review_id for r in data["data"])

@pytest.mark.asyncio
async def test_patch_service_review_status_success(client, create_service_review):
    review = await create_service_review()
    response = client.patch(f"/review/service/{review.service_review_id}", params={"status": 0})
    assert response.status_code == 200
    data = response.json()
    assert data["status"] is True
    assert data["data"]["status"] == 0

@pytest.mark.asyncio
async def test_patch_service_review_status_invalid_status(client, create_service_review):
    review = await create_service_review()
    response = client.patch(f"/review/service/{review.service_review_id}", params={"status": 999})
    assert response.status_code == 400
    data = response.json()
    assert "Invalid status value" in data["detail"]

@pytest.mark.asyncio
async def test_patch_service_review_status_not_found(client):
    response = client.patch("/review/service/999999", params={"status": 1})
    assert response.status_code == 404
    data = response.json()
    assert "not found" in data["detail"].lower()


@pytest.mark.asyncio
async def test_store_rating_recalculation_on_review_approval(db_session, create_store, create_customer):
    """Тест перерахунку рейтингу магазину при затвердженні відгуку"""
    store = await create_store(name="Rating Test Store")
    customer1 = await create_customer(name="Customer 1")
    customer2 = await create_customer(name="Customer 2")
    
    # Створюємо відгуки з різними рейтингами
    review1 = StoreReview(
        store_id=store.store_id,
        customer_id=customer1.customer_id,
        text="Great store!",
        rating=5,
        status=ReviewStatusEnum.approved,  # Затверджений
        ip="127.0.0.1"
    )
    review2 = StoreReview(
        store_id=store.store_id,
        customer_id=customer2.customer_id,
        text="Good store",
        rating=3,
        status=ReviewStatusEnum.new,  # Новий (не затверджений)
        ip="127.0.0.1"
    )
    
    db_session.add_all([review1, review2])
    await db_session.flush()
    await db_session.refresh(review1)
    await db_session.refresh(review2)
    
    # Імпортуємо ReviewService для тестування
    from src.review.service import ReviewService
    
    # Затверджуємо другий відгук - це має запустити перерахунок рейтингу
    await ReviewService.update_store_review_status(db_session, review2.store_review_id, ReviewStatusEnum.approved)
    
    # Перевіряємо, що рейтинг магазину оновився
    await db_session.refresh(store)
    # Середній рейтинг: (5 + 3) / 2 = 4
    assert store.rating == 4


@pytest.mark.asyncio  
async def test_manufacturer_rating_recalculation_on_review_approval(db_session, create_manufacturer, create_customer):
    """Тест перерахунку рейтингу виробника при затвердженні відгуку"""
    manufacturer = await create_manufacturer(name="Rating Test Manufacturer")
    customer1 = await create_customer(name="Customer 1") 
    customer2 = await create_customer(name="Customer 2")
    
    # Створюємо відгуки з різними рейтингами
    review1 = ManufacturerReview(
        manufacturer_id=manufacturer.manufacturer_id,
        customer_id=customer1.customer_id,
        text="Excellent manufacturer!",
        rating=5,
        status=ReviewStatusEnum.approved,
        ip="127.0.0.1"
    )
    review2 = ManufacturerReview(
        manufacturer_id=manufacturer.manufacturer_id,
        customer_id=customer2.customer_id,
        text="Average quality",
        rating=2,
        status=ReviewStatusEnum.new,
        ip="127.0.0.1"
    )
    
    db_session.add_all([review1, review2])
    await db_session.flush()
    await db_session.refresh(review1)
    await db_session.refresh(review2)
    
    from src.review.service import ReviewService
    
    # Затверджуємо другий відгук
    await ReviewService.update_manufacturer_review_status(db_session, review2.manufacturer_review_id, ReviewStatusEnum.approved)
    
    # Перевіряємо рейтинг виробника: (5 + 2) / 2 = 3.5
    await db_session.refresh(manufacturer)
    assert manufacturer.rating == 3.5