"""Cemetery Map setup endpoints (INDL-49).

Exposes two routers with distinct prefixes:
  - accounts_router          → PATCH /api/v1/accounts/me/map-config
  - cemetery_map_router      → /api/v1/settings/cemetery-map[/geocode|/image]
"""
import logging

import boto3
from botocore.exceptions import BotoCoreError, ClientError
from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.cemetery_map.schemas import (
    CemeteryMapConfigResponse,
    GeocodeResponse,
    MapConfigRequest,
)
from src.apps.cemetery_map.services.geocoding_service import GeocodingService
from src.apps.cemetery_map.services.map_config_service import MapConfigService
from src.core.config import settings
from src.core.constants import UserRole
from src.core.dependencies import require_min_role, require_roles, require_tenant
from src.core.exceptions import RateLimitError, ValidationError
from src.core.schemas.response import success
from src.database.session import get_db

logger = logging.getLogger(__name__)

accounts_router = APIRouter(prefix="/accounts", tags=["Cemetery Map"])
cemetery_map_router = APIRouter(prefix="/settings/cemetery-map", tags=["Cemetery Map"])

# Backdrop image upload constraints (V12.1.1 / S-11).
_IMAGE_MAX_BYTES = 10 * 1024 * 1024
_MAGIC_JPEG = b"\xff\xd8\xff"
_MAGIC_PNG = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"

# Geocode per-user rate limit (API6): 30 requests / minute.
_GEOCODE_WINDOW = 60
_GEOCODE_MAX = 30


# ── PATCH /accounts/me/map-config (administrator only, AC-02/AC-03) ─────────────
@accounts_router.patch("/me/map-config", response_model=dict)
async def set_map_config(
    body: MapConfigRequest,
    current_user=Depends(require_min_role(UserRole.ADMINISTRATOR)),
    db: AsyncSession = Depends(get_db),
):
    service = MapConfigService(db)
    config = await service.set_map_config(
        tenant_id=current_user.tenant_id,
        data=body.model_dump(exclude_unset=True),
    )
    return success(
        data=CemeteryMapConfigResponse(**config).model_dump(),
        message="Cemetery map configuration saved",
    )


# ── GET /settings/cemetery-map (read config, view_only+) ───────────────────────
@cemetery_map_router.get("", response_model=dict)
async def get_cemetery_map_config(
    current_user=Depends(
        require_roles(
            UserRole.ADMINISTRATOR, UserRole.MANAGER,
            UserRole.STAFF, UserRole.VIEW_ONLY,
        )
    ),
    db: AsyncSession = Depends(get_db),
):
    service = MapConfigService(db)
    config = await service.get_config(current_user.tenant_id)
    return success(data=CemeteryMapConfigResponse(**config).model_dump())


# ── GET /settings/cemetery-map/geocode (administrator/manager, AC-16/17/18) ─────
@cemetery_map_router.get("/geocode", response_model=dict)
async def geocode_address(
    address: str = Query(..., min_length=1, max_length=500),
    current_user=Depends(require_min_role(UserRole.MANAGER)),
    db: AsyncSession = Depends(get_db),
):
    await _check_geocode_rate_limit(current_user.id)
    service = GeocodingService(db)
    result = await service.geocode(address)
    return success(data=GeocodeResponse(**result).model_dump())


# ── POST /settings/cemetery-map/image (administrator, backdrop upload) ──────────
@cemetery_map_router.post("/image", response_model=dict, status_code=201)
async def upload_map_image(
    request: Request,
    file: UploadFile = File(...),
    current_user=Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    content = await file.read()
    if len(content) > _IMAGE_MAX_BYTES:
        raise ValidationError("File must be under 10MB")
    # Validate by magic bytes, not the client-supplied Content-Type (S-11/V12.2.1).
    if not (content.startswith(_MAGIC_JPEG) or content.startswith(_MAGIC_PNG)):
        raise ValidationError("Only JPG or PNG files are accepted")

    ext = "png" if content.startswith(_MAGIC_PNG) else "jpg"
    s3_configured = bool(settings.AWS_ACCESS_KEY_ID and settings.AWS_SECRET_ACCESS_KEY)
    image_url = None
    if s3_configured:
        s3_key = f"tenants/{tenant_id}/map/backdrop.{ext}"
        try:
            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,
            )
            s3.put_object(
                Bucket=settings.S3_BUCKET, Key=s3_key, Body=content,
                ContentType=f"image/{'png' if ext == 'png' else 'jpeg'}",
            )
            image_url = f"https://s3.{settings.AWS_REGION}.amazonaws.com/{settings.S3_BUCKET}/{s3_key}"
        except (BotoCoreError, ClientError) as exc:
            logger.warning("[cemetery-map/image] S3 upload failed: %s", exc)
            image_url = None

    service = MapConfigService(db)
    if image_url:
        await service.set_map_image(tenant_id, image_url)

    return success(
        data={"image_url": image_url},
        message="Backdrop image uploaded" if image_url
        else "Image accepted; S3 not configured so no URL was stored",
    )


async def _check_geocode_rate_limit(user_id) -> None:
    """Per-user geocode rate limit (fails open without Redis)."""
    from src.database.session import get_redis
    client = await get_redis()
    if client is None:
        return
    try:
        key = f"geocode_rl:{user_id}"
        count = await client.incr(key)
        if count == 1:
            await client.expire(key, _GEOCODE_WINDOW)
        if count > _GEOCODE_MAX:
            ttl = await client.ttl(key)
            raise RateLimitError(
                "Too many address searches — please wait a moment.",
                retry_after=max(int(ttl), 1),
            )
    finally:
        try:
            await client.aclose()
        except Exception:
            pass
