"""Pricing application services — Phase 10.4."""
from __future__ import annotations

from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import TransactionalEventPublisher
from app.events.types import DeliveryEventType
from app.models.pricing import CapabilityBundle, CapabilityDefinition, PricingRule
from app.repositories.foundation import DeliveryOrganizationRepository
from app.repositories.pricing import (
    CapabilityBundleRepository,
    CapabilityDefinitionRepository,
    PricingRuleRepository,
)
from app.schemas.pricing import (
    BundleCreate,
    BundleUpdate,
    CapabilityCreate,
    CapabilityUpdate,
    PricingRuleCreate,
    PricingRuleUpdate,
)
from app.services._helpers import actor_id
from app.specifications.pricing import (
    BundleListSpec,
    CapabilityListSpec,
    PricingRuleListSpec,
)
from app.validators import ensure_optimistic_version, validate_code
from app.validators.pricing import (
    validate_base_amount,
    validate_bundle_name,
    validate_capability_name,
    validate_currency,
    validate_pricing_name,
)
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class PricingRuleService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = PricingRuleRepository(session)
        self.orgs = DeliveryOrganizationRepository(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: PricingRuleCreate, *, actor: CurrentUser | None = None
    ) -> PricingRule:
        if await self.orgs.get(tenant_id, body.organization_id) is None:
            raise NotFoundError("سازمان یافت نشد")
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, code):
            raise AppError("کد قیمت‌گذاری تکراری است", error_code="duplicate_code", status_code=409)
        entity = PricingRule(
            tenant_id=tenant_id,
            organization_id=body.organization_id,
            code=code,
            name=validate_pricing_name(body.name),
            status=body.status,
            base_amount=validate_base_amount(body.base_amount),
            currency_code=validate_currency(body.currency_code),
            rule_config=body.rule_config,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.repo.add(entity)
        await self.publisher.publish(
            event_type=DeliveryEventType.PRICING_RULE_CREATED,
            aggregate_type="pricing_rule",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, rule_id: UUID) -> PricingRule:
        entity = await self.repo.get(tenant_id, rule_id)
        if entity is None:
            raise NotFoundError("قانون قیمت‌گذاری یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20, spec: PricingRuleListSpec | None = None
    ) -> tuple[list[PricingRule], int]:
        items = list(await self.repo.list_filtered(tenant_id, offset=offset, limit=limit, spec=spec))
        return items, await self.repo.count_filtered(tenant_id, spec=spec)

    async def update(
        self, tenant_id: UUID, rule_id: UUID, body: PricingRuleUpdate, *, actor: CurrentUser | None = None
    ) -> PricingRule:
        entity = await self.get(tenant_id, rule_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "name" in data and data["name"] is not None:
            data["name"] = validate_pricing_name(data["name"])
        if "base_amount" in data and data["base_amount"] is not None:
            data["base_amount"] = validate_base_amount(data["base_amount"])
        if "currency_code" in data and data["currency_code"] is not None:
            data["currency_code"] = validate_currency(data["currency_code"])
        for key, value in data.items():
            setattr(entity, key, value)
        entity.version += 1
        entity.updated_by = actor_id(actor)
        await self.publisher.publish(
            event_type=DeliveryEventType.PRICING_RULE_UPDATED,
            aggregate_type="pricing_rule",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def soft_delete(
        self, tenant_id: UUID, rule_id: UUID, *, actor: CurrentUser | None = None
    ) -> PricingRule:
        entity = await self.get(tenant_id, rule_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.publisher.publish(
            event_type=DeliveryEventType.PRICING_RULE_DELETED,
            aggregate_type="pricing_rule",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class CapabilityService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = CapabilityDefinitionRepository(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: CapabilityCreate, *, actor: CurrentUser | None = None
    ) -> CapabilityDefinition:
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, code):
            raise AppError("کد قابلیت تکراری است", error_code="duplicate_code", status_code=409)
        entity = CapabilityDefinition(
            tenant_id=tenant_id,
            code=code,
            name=validate_capability_name(body.name),
            description=body.description,
            status=body.status,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.repo.add(entity)
        await self.publisher.publish(
            event_type=DeliveryEventType.CAPABILITY_CREATED,
            aggregate_type="capability",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, cap_id: UUID) -> CapabilityDefinition:
        entity = await self.repo.get(tenant_id, cap_id)
        if entity is None:
            raise NotFoundError("قابلیت یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20, spec: CapabilityListSpec | None = None
    ) -> tuple[list[CapabilityDefinition], int]:
        items = list(await self.repo.list_filtered(tenant_id, offset=offset, limit=limit, spec=spec))
        return items, await self.repo.count_filtered(tenant_id, spec=spec)

    async def update(
        self, tenant_id: UUID, cap_id: UUID, body: CapabilityUpdate, *, actor: CurrentUser | None = None
    ) -> CapabilityDefinition:
        entity = await self.get(tenant_id, cap_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "name" in data and data["name"] is not None:
            data["name"] = validate_capability_name(data["name"])
        for key, value in data.items():
            setattr(entity, key, value)
        entity.version += 1
        entity.updated_by = actor_id(actor)
        await self.publisher.publish(
            event_type=DeliveryEventType.CAPABILITY_UPDATED,
            aggregate_type="capability",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def soft_delete(
        self, tenant_id: UUID, cap_id: UUID, *, actor: CurrentUser | None = None
    ) -> CapabilityDefinition:
        entity = await self.get(tenant_id, cap_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.publisher.publish(
            event_type=DeliveryEventType.CAPABILITY_DELETED,
            aggregate_type="capability",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class BundleService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = CapabilityBundleRepository(session)
        self.rules = PricingRuleRepository(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: BundleCreate, *, actor: CurrentUser | None = None
    ) -> CapabilityBundle:
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, code):
            raise AppError("کد بسته تکراری است", error_code="duplicate_code", status_code=409)
        if body.pricing_rule_id and await self.rules.get(tenant_id, body.pricing_rule_id) is None:
            raise NotFoundError("قانون قیمت‌گذاری یافت نشد")
        entity = CapabilityBundle(
            tenant_id=tenant_id,
            code=code,
            name=validate_bundle_name(body.name),
            status=body.status,
            capability_codes=body.capability_codes,
            pricing_rule_id=body.pricing_rule_id,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.repo.add(entity)
        await self.publisher.publish(
            event_type=DeliveryEventType.BUNDLE_CREATED,
            aggregate_type="bundle",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, bundle_id: UUID) -> CapabilityBundle:
        entity = await self.repo.get(tenant_id, bundle_id)
        if entity is None:
            raise NotFoundError("بسته قابلیت یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20, spec: BundleListSpec | None = None
    ) -> tuple[list[CapabilityBundle], int]:
        items = list(await self.repo.list_filtered(tenant_id, offset=offset, limit=limit, spec=spec))
        return items, await self.repo.count_filtered(tenant_id, spec=spec)

    async def update(
        self, tenant_id: UUID, bundle_id: UUID, body: BundleUpdate, *, actor: CurrentUser | None = None
    ) -> CapabilityBundle:
        entity = await self.get(tenant_id, bundle_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "name" in data and data["name"] is not None:
            data["name"] = validate_bundle_name(data["name"])
        if "pricing_rule_id" in data and data["pricing_rule_id"]:
            if await self.rules.get(tenant_id, data["pricing_rule_id"]) is None:
                raise NotFoundError("قانون قیمت‌گذاری یافت نشد")
        for key, value in data.items():
            setattr(entity, key, value)
        entity.version += 1
        entity.updated_by = actor_id(actor)
        await self.publisher.publish(
            event_type=DeliveryEventType.BUNDLE_UPDATED,
            aggregate_type="bundle",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def soft_delete(
        self, tenant_id: UUID, bundle_id: UUID, *, actor: CurrentUser | None = None
    ) -> CapabilityBundle:
        entity = await self.get(tenant_id, bundle_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.publisher.publish(
            event_type=DeliveryEventType.BUNDLE_DELETED,
            aggregate_type="bundle",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity
