"""سرویس مدیریت Tenant."""
from __future__ import annotations

from typing import Sequence
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.cache import cache, tenant_metadata_key
from app.models.enums import DomainType, TenantStatus
from app.models.tenant import Tenant
from app.repositories.tenant import DomainRepository, TenantRepository
from app.schemas.tenant import AdminTenantCreate, TenantCreate, TenantUpdate
from app.services.event_service import EventService
from shared.auth import PlatformRole, has_role
from shared.events import CoreEventType
from shared.exceptions import ConflictError, ForbiddenError, NotFoundError
from shared.security import CurrentUser


class TenantService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = TenantRepository(session)
        self.domain_repo = DomainRepository(session)
        self.events = EventService(session)

    async def create(self, data: TenantCreate) -> Tenant:
        # slug باید در کل پلتفرم یکتا باشد.
        if await self.repo.slug_exists(data.slug):
            raise ConflictError(f"slug تکراری است: {data.slug}", error_code="slug_taken")

        tenant = Tenant(
            name=data.name,
            slug=data.slug,
            owner_user_id=data.owner_user_id,
            status=TenantStatus.ACTIVE,
        )
        await self.repo.add(tenant)
        if data.custom_domain:
            await self._add_custom_domain(tenant.id, data.custom_domain)
        await self.events.record(
            event_type=CoreEventType.TENANT_CREATED.value,
            aggregate_type="tenant",
            aggregate_id=str(tenant.id),
            tenant_id=tenant.id,
            payload={"slug": tenant.slug, "name": tenant.name},
        )
        await self.session.commit()
        await self.session.refresh(tenant)
        return tenant

    async def create_for_user(self, user: CurrentUser, data: AdminTenantCreate) -> Tenant:
        owner_id = UUID(user.user_id)
        return await self.create(
            TenantCreate(
                name=data.name,
                slug=data.slug,
                owner_user_id=owner_id,
                custom_domain=data.custom_domain,
            )
        )

    async def _add_custom_domain(self, tenant_id: UUID, domain: str) -> None:
        if await self.domain_repo.domain_exists(domain):
            raise ConflictError(
                f"دامنه تکراری است: {domain}", error_code="domain_taken"
            )
        from app.models.domain import Domain

        await self.domain_repo.add(
            Domain(
                tenant_id=tenant_id,
                domain=domain,
                domain_type=DomainType.CUSTOM_DOMAIN,
            )
        )

    async def get(self, tenant_id: UUID) -> Tenant:
        tenant = await self.repo.get(tenant_id)
        if tenant is None:
            raise NotFoundError(f"tenant یافت نشد: {tenant_id}")
        return tenant

    async def get_by_slug(self, slug: str) -> Tenant | None:
        return await self.repo.get_by_slug(slug)

    async def list(self, *, offset: int, limit: int) -> tuple[Sequence[Tenant], int]:
        items = await self.repo.list(offset=offset, limit=limit)
        total = await self.repo.count()
        return items, total

    async def list_for_user(
        self, user: CurrentUser, *, offset: int, limit: int
    ) -> tuple[Sequence[Tenant], int]:
        if has_role(user, PlatformRole.PLATFORM_ADMIN):
            return await self.list(offset=offset, limit=limit)
        owner_id = UUID(user.user_id)
        items = await self.repo.list_by_owner(owner_id, offset=offset, limit=limit)
        total = await self.repo.count_by_owner(owner_id)
        return items, total

    def ensure_access(self, tenant: Tenant, user: CurrentUser) -> None:
        if has_role(user, PlatformRole.PLATFORM_ADMIN):
            return
        if str(tenant.owner_user_id) != user.user_id:
            raise ForbiddenError("دسترسی به این tenant ندارید")

    async def update(
        self, tenant_id: UUID, data: TenantUpdate, user: CurrentUser | None = None
    ) -> Tenant:
        tenant = await self.get(tenant_id)
        if user is not None:
            self.ensure_access(tenant, user)
        if data.name is not None:
            tenant.name = data.name
        if data.status is not None:
            tenant.status = data.status
        if data.owner_user_id is not None:
            tenant.owner_user_id = data.owner_user_id
        await self.session.commit()
        await self.session.refresh(tenant)
        await self._invalidate_cache(tenant_id)
        return tenant

    async def suspend(self, tenant_id: UUID) -> Tenant:
        tenant = await self.get(tenant_id)
        tenant.status = TenantStatus.SUSPENDED
        await self.events.record(
            event_type=CoreEventType.TENANT_SUSPENDED.value,
            aggregate_type="tenant",
            aggregate_id=str(tenant.id),
            tenant_id=tenant.id,
        )
        await self.session.commit()
        await self.session.refresh(tenant)
        await self._invalidate_cache(tenant_id)
        return tenant

    async def activate(self, tenant_id: UUID) -> Tenant:
        tenant = await self.get(tenant_id)
        tenant.status = TenantStatus.ACTIVE
        await self.events.record(
            event_type=CoreEventType.TENANT_ACTIVATED.value,
            aggregate_type="tenant",
            aggregate_id=str(tenant.id),
            tenant_id=tenant.id,
        )
        await self.session.commit()
        await self.session.refresh(tenant)
        await self._invalidate_cache(tenant_id)
        return tenant

    async def _invalidate_cache(self, tenant_id: UUID) -> None:
        await cache.delete(tenant_metadata_key(str(tenant_id)))
        await cache.delete_pattern(f"feature_access:{tenant_id}:*")
