"""Staff-uploaded signed contract document service — INDL-56.

Handles the wet-signed/scanned copy of a contract that staff upload back to
S3 once a purchaser has physically signed. This is a distinct artifact from
``Contract.pdf_s3_key`` (the system-generated contract PDF produced by the
ARQ worker) — see ``contracts.signed_document_s3_key``.

Uses the same bucket/region as the existing system-generated contract PDF
(``settings.S3_BUCKET`` / ``settings.AWS_REGION``, inline ``boto3.client(...)``,
mirroring ``router.py``'s ``get_contract_pdf_url``) rather than
``document_service.py``'s ``S3_DOCUMENTS_BUCKET``/``S3_DOCUMENTS_REGION``, so
both PDFs for a given contract live in the same place.
"""
from __future__ import annotations

import asyncio
import logging
import uuid
from datetime import datetime, timezone
from uuid import UUID

import boto3
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.config import settings
from src.core.exceptions import ConflictError, NotFoundError, ValidationError
from src.apps.sales.models.contract import Contract

logger = logging.getLogger(__name__)

MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024  # 20 MB
ALLOWED_MIME_TYPE = "application/pdf"
PDF_MAGIC_BYTES = b"%PDF"


def _make_s3_client():
    """Create a boto3 S3 client, matching router.py's get_contract_pdf_url pattern."""
    return 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,
    )


def _build_signed_document_key(tenant_id: UUID, contract_id: UUID) -> str:
    return f"tenant/{tenant_id}/contracts/{contract_id}/signed/{uuid.uuid4()}.pdf"


class ContractDocumentService:

    @staticmethod
    async def upload_signed_document(
        db: AsyncSession,
        tenant_id: str,
        contract_id: UUID,
        current_user,
        file_content: bytes,
        filename: str,
        mime_type: str,
    ) -> Contract:
        """Proxy-upload the staff-supplied signed contract PDF to S3.

        Rejects if the contract has not been signed yet, if the file is not a
        PDF (checked by both the declared content-type and sniffed magic
        bytes), or if it exceeds the 20 MB cap. Replaces (and deletes) any
        prior signed document for this contract.
        """
        locked = await db.execute(
            select(Contract)
            .where(
                Contract.id == contract_id,
                Contract.tenant_id == tenant_id,
                Contract.deleted_at.is_(None),
            )
            .with_for_update()
        )
        contract = locked.scalar_one_or_none()
        if not contract:
            raise NotFoundError("Contract not found.")

        if contract.signed_at is None:
            raise ConflictError(
                "Contract has not been signed yet — nothing to attach a signed document to."
            )

        if mime_type != ALLOWED_MIME_TYPE:
            raise ValidationError(
                message=f"Unsupported file type '{mime_type}'. Only PDF files are allowed."
            )

        if not file_content.startswith(PDF_MAGIC_BYTES):
            raise ValidationError(
                message="File content does not look like a valid PDF."
            )

        if len(file_content) > MAX_FILE_SIZE_BYTES:
            raise ValidationError(message="File size exceeds the 20 MB limit.")

        s3 = _make_s3_client()
        prior_key = contract.signed_document_s3_key

        # Write the new object and commit the DB row FIRST — only delete the
        # prior object once the replacement is durable, so a failed upload
        # never leaves the contract pointing at a deleted key (AC-18/V7).
        new_key = _build_signed_document_key(tenant_id, contract.id)
        await asyncio.to_thread(
            s3.put_object,
            Bucket=settings.S3_BUCKET,
            Key=new_key,
            Body=file_content,
            ContentType=ALLOWED_MIME_TYPE,
        )

        contract.signed_document_s3_key = new_key
        contract.signed_document_uploaded_at = datetime.now(timezone.utc)
        contract.signed_document_uploaded_by = current_user.id
        await db.flush()
        logger.info(
            "[upload_signed_document] Contract %s signed document %s by user %s.",
            contract.id,
            "replaced" if prior_key else "uploaded",
            current_user.id,
        )

        if prior_key:
            try:
                await asyncio.to_thread(
                    s3.delete_object,
                    Bucket=settings.S3_BUCKET,
                    Key=prior_key,
                )
            except Exception as exc:  # noqa: BLE001
                logger.warning(
                    "[upload_signed_document] Failed to delete prior signed document "
                    "for contract %s (key=%s): %s",
                    contract.id,
                    prior_key,
                    exc,
                )

        return contract

    @staticmethod
    async def get_signed_document_url(
        db: AsyncSession,
        tenant_id: str,
        contract_id: UUID,
        current_user,
    ) -> str:
        """Return a short-lived presigned S3 GET URL for the uploaded signed document."""
        result = await db.execute(
            select(Contract).where(
                Contract.id == contract_id,
                Contract.tenant_id == tenant_id,
                Contract.deleted_at.is_(None),
            )
        )
        contract = result.scalar_one_or_none()
        if not contract or not contract.signed_document_s3_key:
            raise NotFoundError("Signed document not found.")

        s3 = _make_s3_client()
        presigned_url: str = await asyncio.to_thread(
            s3.generate_presigned_url,
            "get_object",
            Params={
                "Bucket": settings.S3_BUCKET,
                "Key": contract.signed_document_s3_key,
            },
            ExpiresIn=600,
        )
        return presigned_url
