"""Routing application services — Phase 10.6."""
from __future__ import annotations

from datetime import datetime, timezone
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.routing import OptimizationRun, RoutePlan, RouteStop
from app.models.types import OptimizationRunStatus, RoutePlanStatus
from app.repositories.foundation import DeliveryOrganizationRepository
from app.repositories.routing import (
    OptimizationRunRepository,
    RoutePlanRepository,
    RouteStopRepository,
)
from app.schemas.routing import (
    OptimizationRunCreate,
    RoutePlanCreate,
    RoutePlanUpdate,
    RouteStopCreate,
)
from app.services._helpers import actor_id
from app.specifications.routing import OptimizationRunListSpec, RoutePlanListSpec
from app.validators import ensure_optimistic_version, validate_code
from app.validators.routing import validate_stop_sequence
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class RoutePlanService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = RoutePlanRepository(session)
        self.stops = RouteStopRepository(session)
        self.orgs = DeliveryOrganizationRepository(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: RoutePlanCreate, *, actor: CurrentUser | None = None
    ) -> RoutePlan:
        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 = RoutePlan(
            tenant_id=tenant_id,
            organization_id=body.organization_id,
            code=code,
            driver_id=body.driver_id,
            dispatch_job_ids=body.dispatch_job_ids,
            routing_engine_ref=body.routing_engine_ref,
            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.ROUTE_PLAN_CREATED,
            aggregate_type="route_plan",
            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, plan_id: UUID) -> RoutePlan:
        entity = await self.repo.get(tenant_id, plan_id)
        if entity is None:
            raise NotFoundError("برنامه مسیر یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20, spec: RoutePlanListSpec | None = None
    ) -> tuple[list[RoutePlan], 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, plan_id: UUID, body: RoutePlanUpdate, *, actor: CurrentUser | None = None
    ) -> RoutePlan:
        entity = await self.get(tenant_id, plan_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        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.ROUTE_PLAN_UPDATED,
            aggregate_type="route_plan",
            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, plan_id: UUID, *, actor: CurrentUser | None = None
    ) -> RoutePlan:
        entity = await self.get(tenant_id, plan_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.publisher.publish(
            event_type=DeliveryEventType.ROUTE_PLAN_DELETED,
            aggregate_type="route_plan",
            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 add_stop(
        self,
        tenant_id: UUID,
        plan_id: UUID,
        body: RouteStopCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> RouteStop:
        await self.get(tenant_id, plan_id)
        validate_stop_sequence(body.sequence)
        entity = RouteStop(
            tenant_id=tenant_id,
            route_plan_id=plan_id,
            sequence=body.sequence,
            address_ref=body.address_ref,
            external_order_ref=body.external_order_ref,
            notes=body.notes,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.stops.add(entity)
        await self.publisher.publish(
            event_type=DeliveryEventType.ROUTE_STOP_ADDED,
            aggregate_type="route_stop",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"route_plan_id": str(plan_id), "sequence": body.sequence},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_stops(self, tenant_id: UUID, plan_id: UUID) -> list[RouteStop]:
        await self.get(tenant_id, plan_id)
        return list(await self.stops.list_for_plan(tenant_id, plan_id))


class OptimizationRunService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = OptimizationRunRepository(session)
        self.plans = RoutePlanRepository(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def start(
        self, tenant_id: UUID, body: OptimizationRunCreate, *, actor: CurrentUser | None = None
    ) -> OptimizationRun:
        if body.route_plan_id and await self.plans.get(tenant_id, body.route_plan_id) is None:
            raise NotFoundError("برنامه مسیر یافت نشد")
        entity = OptimizationRun(
            tenant_id=tenant_id,
            route_plan_id=body.route_plan_id,
            status=OptimizationRunStatus.RUNNING,
            routing_engine_ref=body.routing_engine_ref,
            request_payload=body.request_payload,
            started_at=datetime.now(timezone.utc),
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.repo.add(entity)
        await self.publisher.publish(
            event_type=DeliveryEventType.OPTIMIZATION_RUN_STARTED,
            aggregate_type="optimization_run",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"route_plan_id": str(body.route_plan_id) if body.route_plan_id else None},
        )
        entity.status = OptimizationRunStatus.COMPLETED
        entity.completed_at = datetime.now(timezone.utc)
        entity.result_payload = {"status": "stub", "message": "optimization stub"}
        await self.publisher.publish(
            event_type=DeliveryEventType.OPTIMIZATION_RUN_COMPLETED,
            aggregate_type="optimization_run",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"route_plan_id": str(body.route_plan_id) if body.route_plan_id else None},
        )
        if body.route_plan_id:
            plan = await self.plans.get(tenant_id, body.route_plan_id)
            if plan is not None:
                plan.status = RoutePlanStatus.PLANNED
                plan.planned_at = datetime.now(timezone.utc)
                plan.version += 1
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, run_id: UUID) -> OptimizationRun:
        entity = await self.repo.get(tenant_id, run_id)
        if entity is None:
            raise NotFoundError("اجرای بهینه‌سازی یافت نشد")
        return entity

    async def list(
        self,
        tenant_id: UUID,
        *,
        offset: int = 0,
        limit: int = 20,
        spec: OptimizationRunListSpec | None = None,
    ) -> tuple[list[OptimizationRun], 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)
