"""QR Code service — upsert, enqueue generation, presigned download."""
from __future__ import annotations

import asyncio
from typing import Optional
from urllib.parse import quote
from uuid import UUID

from sqlalchemy import select, func
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.config import settings
from src.core.exceptions import NotFoundError
from src.apps.settings.models.qr_code import QRCode
from src.apps.tenants.models.account import Account


class QRCodeService:

    @staticmethod
    async def get_list(
        db: AsyncSession,
        tenant_id: str,
        qr_type: Optional[str] = None,
        page: int = 1,
        page_size: int = 20,
    ) -> tuple[list[QRCode], int]:
        filters = [QRCode.tenant_id == tenant_id]
        if qr_type:
            filters.append(QRCode.qr_type == qr_type)

        total = (
            await db.execute(
                select(func.count()).select_from(QRCode).where(*filters)
            )
        ).scalar_one()

        offset = (page - 1) * page_size
        result = await db.execute(
            select(QRCode)
            .where(*filters)
            .order_by(QRCode.qr_type, QRCode.reference_id)
            .offset(offset)
            .limit(page_size)
        )
        items = list(result.scalars().all())
        return items, total

    @staticmethod
    async def generate_one(
        db: AsyncSession,
        tenant_id: str,
        qr_type: str,
        reference_id: str,
        display_label: Optional[str] = None,
        arq_redis=None,
    ) -> dict:
        # For plot type, validate the plot exists
        if qr_type == "plot":
            from src.apps.plots.models.plot import Plot
            plot_stmt = select(Plot).where(
                Plot.tenant_id == tenant_id,
                Plot.plot_ref == reference_id,
            )
            plot_result = await db.execute(plot_stmt)
            if not plot_result.scalar_one_or_none():
                raise NotFoundError("Plot not found")

        # Get tenant subdomain for content_url
        acct_result = await db.execute(
            select(Account).where(Account.id == tenant_id)
        )
        account = acct_result.scalar_one_or_none()
        subdomain = account.subdomain if account else str(tenant_id)

        content_url = QRCodeService.build_content_url(subdomain, qr_type, reference_id)

        # Upsert: INSERT … ON CONFLICT DO UPDATE — only set content_url on INSERT
        stmt = (
            pg_insert(QRCode)
            .values(
                tenant_id=tenant_id,
                qr_type=qr_type,
                reference_id=reference_id,
                display_label=display_label,
                content_url=content_url,
                is_active=True,
            )
            .on_conflict_do_update(
                constraint="uq_qr_codes_tenant_type_ref",
                set_={
                    "display_label": display_label,
                    "is_active": True,
                },
            )
            .returning(QRCode.id)
        )
        result = await db.execute(stmt)
        qr_id = result.scalar_one()
        await db.flush()

        job_id = None
        if arq_redis is not None:
            job = await arq_redis.enqueue_job("generate_qr_code", str(qr_id))
            job_id = job.job_id if job else None

        return {"qr_code_id": str(qr_id), "job_id": job_id}

    @staticmethod
    async def regenerate_all(
        db: AsyncSession,
        tenant_id: str,
        qr_type: Optional[str] = None,
        arq_redis=None,
    ) -> dict:
        filters = [QRCode.tenant_id == tenant_id, QRCode.is_active.is_(True)]
        if qr_type:
            filters.append(QRCode.qr_type == qr_type)

        result = await db.execute(select(QRCode.id).where(*filters))
        ids = [str(row[0]) for row in result.all()]

        enqueued = 0
        if arq_redis is not None:
            for qr_id in ids:
                await arq_redis.enqueue_job("generate_qr_code", qr_id)
                enqueued += 1

        return {"enqueued": enqueued}

    @staticmethod
    async def get_download_url(
        db: AsyncSession,
        tenant_id: str,
        qr_code_id: UUID,
    ) -> str:
        result = await db.execute(
            select(QRCode).where(
                QRCode.id == qr_code_id,
                QRCode.tenant_id == tenant_id,
            )
        )
        qr = result.scalar_one_or_none()
        if not qr:
            raise NotFoundError("QR code not found")

        s3_key = qr.pdf_s3_key or qr.svg_s3_key
        if not s3_key:
            raise NotFoundError("QR code file not yet generated")

        import boto3

        def _presign():
            s3 = boto3.client(
                "s3",
                region_name=settings.AWS_REGION,
                aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
                aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
            )
            return s3.generate_presigned_url(
                "get_object",
                Params={"Bucket": settings.S3_BUCKET, "Key": s3_key},
                ExpiresIn=900,
            )

        url: str = await asyncio.to_thread(_presign)
        return url

    @staticmethod
    def build_content_url(subdomain: str, qr_type: str, reference_id: str) -> str:
        base = f"https://{subdomain}.{settings.APP_DOMAIN}"
        if qr_type == "entrance":
            return f"{base}/"
        if qr_type == "section":
            return f"{base}/find?section={quote(reference_id, safe='')}"
        if qr_type == "plot":
            return f"{base}/find?plot={quote(reference_id, safe='')}"
        if qr_type == "contract":
            return f"{base}/contract/{quote(reference_id, safe='')}"
        return f"{base}/"
