"""Middleware تشخیص Tenant.

ترتیب تشخیص tenant:
    1. هدر X-Tenant-ID
    2. هدر X-Tenant-Slug
    3. Subdomain (از روی Host)
    4. Custom domain (از روی Host)

نتیجه در request.state قرار می‌گیرد:
    request.state.tenant_id
    request.state.tenant_slug

توجه: این middleware اگر tenant پیدا نشد خطا نمی‌دهد؛ صرفاً state را خالی
می‌گذارد. اعمال اجبار روی endpointهای tenant-aware از طریق dependency
(require_tenant) انجام می‌شود.
"""
from __future__ import annotations

from uuid import UUID

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response

from app.core.cache import cache, tenant_metadata_key
from app.core.database import AsyncSessionLocal
from app.core.logging import get_logger
from app.repositories.tenant import DomainRepository, TenantRepository
from shared.tenant import HEADER_TENANT_ID, HEADER_TENANT_SLUG

logger = get_logger(__name__)


class TenantResolutionMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, *, base_domain: str | None = None) -> None:
        super().__init__(app)
        # base_domain برای تشخیص subdomain استفاده می‌شود (مثلاً example.com).
        self.base_domain = base_domain

    async def dispatch(self, request: Request, call_next) -> Response:
        request.state.tenant_id = None
        request.state.tenant_slug = None

        try:
            await self._resolve(request)
        except Exception as exc:  # pragma: no cover - نباید request را بشکند
            logger.warning("tenant_resolution_error", extra={"error": str(exc)})

        return await call_next(request)

    async def _resolve(self, request: Request) -> None:
        # ۱) هدر X-Tenant-ID
        raw_id = request.headers.get(HEADER_TENANT_ID)
        if raw_id:
            try:
                tenant_id = UUID(raw_id)
            except ValueError:
                tenant_id = None
            if tenant_id is not None:
                await self._set_from_tenant_id(request, tenant_id)
                if request.state.tenant_id is not None:
                    return

        # ۲) هدر X-Tenant-Slug
        slug = request.headers.get(HEADER_TENANT_SLUG)
        if slug:
            await self._set_from_slug(request, slug.strip().lower())
            if request.state.tenant_id is not None:
                return

        # ۳ و ۴) subdomain یا custom domain از روی Host
        host = request.headers.get("host", "").split(":")[0].strip().lower()
        if not host:
            return

        # اگر base_domain تعریف شده و host زیردامنه آن است → slug = بخش اول.
        if self.base_domain and host.endswith("." + self.base_domain):
            sub = host[: -(len(self.base_domain) + 1)]
            if sub and sub != "www":
                await self._set_from_slug(request, sub)
                if request.state.tenant_id is not None:
                    return

        # در غیر این صورت host را به‌عنوان custom domain در نظر بگیر.
        await self._set_from_domain(request, host)

    async def _set_from_tenant_id(self, request: Request, tenant_id: UUID) -> None:
        async with AsyncSessionLocal() as session:
            tenant = await TenantRepository(session).get(tenant_id)
            if tenant is not None:
                request.state.tenant_id = tenant.id
                request.state.tenant_slug = tenant.slug

    async def _set_from_slug(self, request: Request, slug: str) -> None:
        async with AsyncSessionLocal() as session:
            tenant = await TenantRepository(session).get_by_slug(slug)
            if tenant is not None:
                request.state.tenant_id = tenant.id
                request.state.tenant_slug = tenant.slug

    async def _set_from_domain(self, request: Request, host: str) -> None:
        async with AsyncSessionLocal() as session:
            domain = await DomainRepository(session).get_by_domain(host)
            if domain is not None:
                tenant = await TenantRepository(session).get(domain.tenant_id)
                if tenant is not None:
                    request.state.tenant_id = tenant.id
                    request.state.tenant_slug = tenant.slug
