import pytest
import logging
from fastapi.testclient import TestClient
from hypothesis import given, strategies as st, settings
from bson import ObjectId
from pos_system.server import app
from pos_system.database import get_db
from datetime import datetime

client = TestClient(app)
logger = logging.getLogger(__name__)


def clear_db():
  db = get_db()
  db.orders.delete_many({})
  db.items.delete_many({})


@pytest.fixture(autouse=True)
def run_around_tests():
  clear_db()
  yield
  clear_db()


def create_test_item():
  response = client.post(
      "/items/",
      json={"name": "Test Item", "price": 10.99,
            "quantity": 100, "unit": "piece"}
  )
  return response.json()["_id"]


def test_create_order():
  logger.info("Testing create order")
  item_id = create_test_item()
  order_data = {
      "user_id": str(ObjectId()),
      "items": [{"item_id": item_id, "quantity": 2}],
      "total_amount": 21.98,
      "payment_method": "credit_card"
  }
  response = client.post("/orders/", json=order_data)
  assert response.status_code == 200
  assert "_id" in response.json()
  logger.info(f"Created order with ID: {response.json()['_id']}")


def test_read_orders():
  logger.info("Testing read orders")
  item_id = create_test_item()
  order_data = {
      "user_id": str(ObjectId()),
      "items": [{"item_id": item_id, "quantity": 2}],
      "total_amount": 21.98,
      "payment_method": "credit_card"
  }
  client.post("/orders/", json=order_data)
  response = client.get("/orders/")
  assert response.status_code == 200
  orders = response.json()
  assert len(orders) == 1
  assert orders[0]["total_amount"] == 21.98
  logger.info(f"Retrieved {len(orders)} orders")


def test_read_order():
  logger.info("Testing read single order")
  item_id = create_test_item()
  order_data = {
      "user_id": str(ObjectId()),
      "items": [{"item_id": item_id, "quantity": 2}],
      "total_amount": 21.98,
      "payment_method": "credit_card"
  }
  create_response = client.post("/orders/", json=order_data)
  order_id = create_response.json()["_id"]
  response = client.get(f"/orders/{order_id}")
  assert response.status_code == 200
  assert response.json()["total_amount"] == 21.98
  logger.info(f"Retrieved order with ID: {order_id}")


def test_update_order():
  logger.info("Testing update order")
  item_id = create_test_item()
  order_data = {
      "user_id": str(ObjectId()),
      "items": [{"item_id": item_id, "quantity": 2}],
      "total_amount": 21.98,
      "payment_method": "credit_card"
  }
  create_response = client.post("/orders/", json=order_data)
  order_id = create_response.json()["_id"]
  update_data = {
      "items": [{"item_id": item_id, "quantity": 3}],
      "total_amount": 32.97,
      "payment_method": "cash"
  }
  response = client.put(f"/orders/{order_id}", json=update_data)
  assert response.status_code == 200
  assert response.json()["total_amount"] == 32.97
  assert response.json()["payment_method"] == "cash"
  logger.info(f"Updated order with ID: {order_id}")


def test_delete_order():
  logger.info("Testing delete order")
  item_id = create_test_item()
  order_data = {
      "user_id": str(ObjectId()),
      "items": [{"item_id": item_id, "quantity": 2}],
      "total_amount": 21.98,
      "payment_method": "credit_card"
  }
  create_response = client.post("/orders/", json=order_data)
  order_id = create_response.json()["_id"]
  response = client.delete(f"/orders/{order_id}")
  assert response.status_code == 200
  assert response.json()["message"] == "Order deleted successfully"
  get_response = client.get(f"/orders/{order_id}")
  assert get_response.status_code == 404
  logger.info(f"Deleted order with ID: {order_id}")


@given(
    st.lists(
        st.fixed_dictionaries({
            "item_id": st.text(min_size=24, max_size=24),
            "quantity": st.integers(min_value=1, max_value=10)
        }),
        min_size=1,
        max_size=5
    ),
    st.floats(min_value=0.01, max_value=1000,
              allow_nan=False, allow_infinity=False),
    st.sampled_from(["credit_card", "cash", "paypal"])
)
@settings(max_examples=50)
def test_create_order_property(items, total_amount, payment_method):
  clear_db()
  item_id = create_test_item()
  for item in items:
    item["item_id"] = item_id  # Use the same item_id for all items
  order_data = {
      "user_id": str(ObjectId()),
      "items": items,
      "total_amount": total_amount,
      "payment_method": payment_method
  }
  response = client.post("/orders/", json=order_data)
  assert response.status_code == 200
  assert "_id" in response.json()
  order_id = response.json()["_id"]
  get_response = client.get(f"/orders/{order_id}")
  assert get_response.status_code == 200
  retrieved_order = get_response.json()
  assert retrieved_order["total_amount"] == total_amount
  assert retrieved_order["payment_method"] == payment_method
  assert len(retrieved_order["items"]) == len(items)


@given(
    st.lists(
        st.fixed_dictionaries({
            "user_id": st.text(min_size=24, max_size=24),
            "total_amount": st.floats(min_value=0.01, max_value=1000, allow_nan=False, allow_infinity=False),
            "payment_method": st.sampled_from(["credit_card", "cash", "paypal"])
        }),
        min_size=1,
        max_size=10
    )
)
@settings(max_examples=20)
def test_read_orders_property(orders):
  clear_db()
  item_id = create_test_item()
  for order in orders:
    order_data = {
        "user_id": order["user_id"],
        "items": [{"item_id": item_id, "quantity": 1}],
        "total_amount": order["total_amount"],
        "payment_method": order["payment_method"]
    }
    client.post("/orders/", json=order_data)
  response = client.get("/orders/")
  assert response.status_code == 200
  retrieved_orders = response.json()
  assert len(retrieved_orders) == len(orders)
  for retrieved_order in retrieved_orders:
    assert "user_id" in retrieved_order
    assert "total_amount" in retrieved_order
    assert "payment_method" in retrieved_order


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
  outcome = yield
  report = outcome.get_result()
  if report.when == "call" and report.failed:
    if "hypothesis" in item.keywords:
      logger.error(f"Property test failed: {item.name}")
      logger.error(f"Falsifying example: {call.excinfo.value}")


if __name__ == "__main__":
  pytest.main([__file__])
