from __future__ import annotations

import datetime as dt
from typing import Any

from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo import ReturnDocument

from fastcheck_api.app.services.rabbitmq_service import rabbitmq_service
from fastcheck_api.app.utils.mongo import jsonable, sanitize_rut, to_object_id


class InsufficientCreditsError(Exception):
    pass


class CheckService:
    @staticmethod
    async def create_single_check(
        *,
        db: AsyncIOMotorDatabase,
        tenant_id: str,
        user_id: str,
        rut: str,
        name: str | None,
        enqueue: bool = True,
    ) -> dict[str, Any]:
        now = dt.datetime.now(dt.timezone.utc)
        tenant_oid = to_object_id(tenant_id)
        rut = sanitize_rut(rut)

        tenant_after = await db["tenants"].find_one_and_update(
            {"_id": tenant_oid, "creditBalance.availableCredits": {"$gte": 1}},
            {
                "$inc": {
                    "creditBalance.availableCredits": -1,
                    "creditBalance.totalCreditsUsed": 1,
                },
                "$set": {
                    "creditBalance.lastCreditOperation": now,
                    "updatedAt": now,
                },
            },
            return_document=ReturnDocument.AFTER,
        )
        if not tenant_after:
            raise InsufficientCreditsError("Insufficient credits")

        new_balance = int((tenant_after.get("creditBalance") or {}).get("availableCredits") or 0)

        await db["creditoperations"].insert_one(
            {
                "tenantId": tenant_oid,
                "userId": to_object_id(user_id),
                "operationType": "evaluation",
                "creditsChanged": -1,
                "balanceAfter": new_balance,
                "description": f"Valutazione singola per {rut}",
                "metadata": {"evaluationType": "single", "supplierRut": rut, "supplierName": name},
                "createdAt": now,
            }
        )

        job_doc: dict[str, Any] = {
            "tenantId": tenant_id,
            "status": "processing",
            "type": "single",
            "createdBy": user_id,
            "totalEvaluations": 1,
            "completedEvaluations": 0,
            "failedEvaluations": 0,
            "supplierData": {"rut": rut, "name": name or "N/A"},
            "createdAt": now,
            "updatedAt": now,
        }
        insert = await db["evaluationjobs"].insert_one(job_doc)
        job_id = str(insert.inserted_id)

        if enqueue:
            await rabbitmq_service.publish_evaluation(
                {"rut": rut, "name": name, "jobId": job_id, "tenantId": tenant_id, "userId": user_id}
            )

        stored = await db["evaluationjobs"].find_one({"_id": insert.inserted_id})
        if not stored:
            raise RuntimeError("Failed to create job")
        return jsonable(stored)

    @staticmethod
    async def list_jobs(*, db: AsyncIOMotorDatabase, tenant_id: str, page: int, limit: int) -> dict[str, Any]:
        skip = (page - 1) * limit
        cursor = (
            db["evaluationjobs"]
            .find({"tenantId": tenant_id})
            .sort([("createdAt", -1)])
            .skip(skip)
            .limit(limit)
        )
        jobs = [jsonable(doc) async for doc in cursor]
        total = await db["evaluationjobs"].count_documents({"tenantId": tenant_id})
        pages = (total + limit - 1) // limit
        return {"jobs": jobs, "total": total, "pages": pages}

    @staticmethod
    async def get_job(*, db: AsyncIOMotorDatabase, tenant_id: str, job_id: str) -> dict[str, Any] | None:
        try:
            oid = ObjectId(job_id)
        except Exception:
            return None
        doc = await db["evaluationjobs"].find_one({"_id": oid, "tenantId": tenant_id})
        return jsonable(doc) if doc else None

    @staticmethod
    async def list_results(
        *, db: AsyncIOMotorDatabase, tenant_id: str, job_id: str, page: int, limit: int
    ) -> dict[str, Any]:
        try:
            oid = ObjectId(job_id)
        except Exception:
            raise ValueError("Invalid job id")

        job = await db["evaluationjobs"].find_one({"_id": oid, "tenantId": tenant_id})
        if not job:
            raise LookupError("Job not found")

        skip = (page - 1) * limit
        cursor = (
            db["evaluationresults"]
            .find({"jobId": oid, "tenantId": tenant_id})
            .sort([("createdAt", -1)])
            .skip(skip)
            .limit(limit)
        )
        results = [jsonable(doc) async for doc in cursor]
        total = await db["evaluationresults"].count_documents({"jobId": oid, "tenantId": tenant_id})
        pages = (total + limit - 1) // limit
        return {"results": results, "total": total, "pages": pages}
