"""Organization application service — Phase 6.1."""
from __future__ import annotations

from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import InMemoryEventPublisher, get_event_publisher
from app.events.types import CrmEventType
from app.models.foundation import Organization
from app.models.types import AuditAction
from app.repositories.foundation import OrganizationRepository
from app.schemas.foundation import OrganizationCreate, OrganizationUpdate
from app.services.audit_service import AuditService
from app.validators import validate_email, validate_non_empty, validate_phone
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


def _apply_update(entity, data: dict) -> None:
    for key, value in data.items():
        setattr(entity, key, value)


class OrganizationService:
    def __init__(
        self,
        session: AsyncSession,
        publisher: InMemoryEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = OrganizationRepository(session)
        self.audit = AuditService(session)
        self.publisher = publisher or get_event_publisher()

    async def create(
        self,
        tenant_id: UUID,
        body: OrganizationCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> Organization:
        actor_id = actor.user_id if actor else None
        name = validate_non_empty(body.name, field="name")
        if await self.repo.get_by_name(tenant_id, name) is not None:
            raise AppError(
                "سازمان با این نام وجود دارد",
                status_code=409,
                error_code="organization_name_exists",
            )
        if body.parent_organization_id is not None:
            parent = await self.repo.get(tenant_id, body.parent_organization_id)
            if parent is None:
                raise NotFoundError(
                    "سازمان والد یافت نشد", error_code="parent_organization_not_found"
                )
        entity = Organization(
            tenant_id=tenant_id,
            name=name,
            kind=body.kind,
            legal_name=body.legal_name,
            trade_name=body.trade_name,
            national_identifier=body.national_identifier,
            tax_number=body.tax_number,
            organization_type_id=body.organization_type_id,
            organization_industry_id=body.organization_industry_id,
            industry=body.industry,
            website=body.website,
            email=validate_email(body.email),
            phone=validate_phone(body.phone),
            parent_organization_id=body.parent_organization_id,
            status=body.status,
            owner_user_id=body.owner_user_id,
            description=body.description,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="organization",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=CrmEventType.ORGANIZATION_CREATED,
            aggregate_type="organization",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"name": entity.name, "kind": entity.kind.value},
        )
        return entity

    async def update(
        self,
        tenant_id: UUID,
        org_id: UUID,
        body: OrganizationUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> Organization:
        entity = await self.repo.get(tenant_id, org_id)
        if entity is None:
            raise NotFoundError("سازمان یافت نشد", error_code="organization_not_found")
        actor_id = actor.user_id if actor else None
        data = body.model_dump(exclude_unset=True)
        if "name" in data:
            data["name"] = validate_non_empty(data["name"], field="name")
            clash = await self.repo.get_by_name(tenant_id, data["name"])
            if clash is not None and clash.id != org_id:
                raise AppError(
                    "سازمان با این نام وجود دارد",
                    status_code=409,
                    error_code="organization_name_exists",
                )
        if "email" in data:
            data["email"] = validate_email(data["email"])
        if "phone" in data:
            data["phone"] = validate_phone(data["phone"])
        if data.get("parent_organization_id") == org_id:
            raise AppError(
                "سازمان نمی‌تواند والد خودش باشد",
                status_code=422,
                error_code="invalid_parent_organization",
            )
        _apply_update(entity, data)
        entity.updated_by = actor_id
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="organization",
            entity_id=entity.id,
            action=AuditAction.UPDATE,
            actor_user_id=actor_id,
            changes=data,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def soft_delete(
        self,
        tenant_id: UUID,
        org_id: UUID,
        *,
        actor: CurrentUser | None = None,
    ) -> Organization:
        entity = await self.repo.get(tenant_id, org_id)
        if entity is None:
            raise NotFoundError("سازمان یافت نشد", error_code="organization_not_found")
        actor_id = actor.user_id if actor else None
        await self.repo.soft_delete(entity, deleted_by=actor_id)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="organization",
            entity_id=entity.id,
            action=AuditAction.DELETE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, org_id: UUID) -> Organization:
        entity = await self.repo.get(tenant_id, org_id)
        if entity is None:
            raise NotFoundError("سازمان یافت نشد", error_code="organization_not_found")
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)
