import logging
from uuid import UUID

from geoalchemy2 import WKTElement
from sqlalchemy import select, text
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.tenants.models.account import Account
from src.core.config import settings
from src.core.exceptions import NotFoundError
from src.core.utils.geometry import geojson_to_wkt, geometry_to_geojson

logger = logging.getLogger(__name__)

_MAP_IMAGE_KEY = "map_image_url"


class MapConfigService:
    """Reads/writes the tenant's cemetery-map configuration on the ``accounts`` row.

    Writes are gated to `administrator` at the route layer. Fields are mapped
    explicitly (never **kwargs) to prevent mass-assignment (security API3)."""

    def __init__(self, db: AsyncSession):
        self.db = db

    async def _get_account(self, tenant_id: UUID) -> Account:
        acc = (await self.db.execute(
            select(Account).where(Account.id == tenant_id)
        )).scalar_one_or_none()
        if acc is None:
            raise NotFoundError("Cemetery not found")
        return acc

    async def _boundary_metrics(self, tenant_id: UUID):
        """Return (area_m2, centroid_lat, centroid_lng) for the account boundary."""
        row = (await self.db.execute(
            text(
                "SELECT ST_Area(boundary) AS area, "
                "ST_Y(ST_Centroid(boundary::geometry)) AS lat, "
                "ST_X(ST_Centroid(boundary::geometry)) AS lng "
                "FROM accounts WHERE id = :aid AND boundary IS NOT NULL"
            ),
            {"aid": str(tenant_id)},
        )).first()
        if not row or row[0] is None:
            return None, None, None
        return round(float(row[0]), 2), float(row[1]), float(row[2])

    async def get_config(self, tenant_id: UUID) -> dict:
        acc = await self._get_account(tenant_id)
        centroid = None
        area = acc.total_area_m2
        if acc.boundary is not None:
            area_calc, lat, lng = await self._boundary_metrics(tenant_id)
            if lat is not None:
                centroid = {"lat": lat, "lng": lng}
        config = acc.config_json or {}
        return {
            "map_mode": acc.map_mode or "image",
            "boundary": geometry_to_geojson(acc.boundary),
            "centroid": centroid,
            "total_area_m2": area,
            "map_image_url": config.get(_MAP_IMAGE_KEY),
            "geocoding_enabled": bool(settings.GOOGLE_MAPS_API_KEY),
            "map_search_lat": acc.map_search_lat,
            "map_search_lng": acc.map_search_lng,
            "map_search_address": acc.map_search_address,
        }

    async def set_map_config(self, tenant_id: UUID, data: dict) -> dict:
        acc = await self._get_account(tenant_id)

        if "map_mode" in data and data["map_mode"] is not None:
            acc.map_mode = data["map_mode"]

        if "boundary" in data:
            boundary = data["boundary"]
            if boundary is None:
                acc.boundary = None
                acc.centroid = None
                acc.total_area_m2 = None
            else:
                wkt = geojson_to_wkt(boundary)  # validates (422)
                acc.boundary = WKTElement(wkt, srid=4326, extended=False)
                # Recompute centroid + area server-side (AC-02).
                metrics = (await self.db.execute(
                    text(
                        "SELECT ST_Area(ST_GeogFromText(:wkt)) AS area, "
                        "ST_AsText(ST_Centroid(ST_GeomFromText(:wkt, 4326))) AS cpt"
                    ),
                    {"wkt": wkt},
                )).first()
                acc.total_area_m2 = round(float(metrics[0]), 2) if metrics and metrics[0] is not None else None
                if metrics and metrics[1]:
                    acc.centroid = WKTElement(metrics[1], srid=4326, extended=False)

        # Last-searched-location cache (AC-19).
        if data.get("search_lat") is not None:
            acc.map_search_lat = data["search_lat"]
        if data.get("search_lng") is not None:
            acc.map_search_lng = data["search_lng"]
        if data.get("search_address") is not None:
            acc.map_search_address = data["search_address"]

        await self.db.flush()
        return await self.get_config(tenant_id)

    async def set_map_image(self, tenant_id: UUID, image_url: str) -> None:
        acc = await self._get_account(tenant_id)
        config = dict(acc.config_json or {})
        config[_MAP_IMAGE_KEY] = image_url
        acc.config_json = config
        # switching to image mode implicitly when a backdrop is uploaded
        acc.map_mode = "image"
        await self.db.flush()
