"""Fleet & vehicle application services — Phase 10.2."""
from __future__ import annotations

from uuid import UUID

from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import TransactionalEventPublisher
from app.events.types import DeliveryEventType
from app.models.fleet import Fleet, Vehicle, VehicleAssignment, VehicleType
from app.models.types import AuditAction, VehicleAssignmentStatus, VehicleStatus
from app.repositories.drivers import DriverRepository
from app.repositories.fleet import (
    FleetRepository,
    VehicleAssignmentRepository,
    VehicleRepository,
    VehicleTypeRepository,
)
from app.repositories.foundation import (
    DeliveryHubRepository,
    DeliveryOrganizationRepository,
)
from app.schemas.fleet import (
    FleetCreate,
    FleetUpdate,
    VehicleAssignmentCreate,
    VehicleAssignmentUpdate,
    VehicleCreate,
    VehicleTypeCreate,
    VehicleTypeUpdate,
    VehicleUpdate,
)
from app.services.audit_service import AuditService
from app.services._helpers import actor_id, jsonable
from app.specifications.fleet import FleetListSpec, VehicleListSpec, VehicleTypeListSpec
from app.validators import ensure_optimistic_version, validate_code
from app.validators.fleet import (
    validate_fleet_name,
    validate_plate_number,
    validate_vehicle_type_name,
)
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class FleetService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = FleetRepository(session)
        self.orgs = DeliveryOrganizationRepository(session)
        self.hubs = DeliveryHubRepository(session)
        self.audit = AuditService(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: FleetCreate, *, actor: CurrentUser | None = None
    ) -> Fleet:
        org = await self.orgs.get(tenant_id, body.organization_id)
        if org is None:
            raise NotFoundError("سازمان یافت نشد")
        if body.hub_id is not None:
            hub = await self.hubs.get(tenant_id, body.hub_id)
            if hub 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 = Fleet(
            tenant_id=tenant_id,
            organization_id=body.organization_id,
            hub_id=body.hub_id,
            code=code,
            name=validate_fleet_name(body.name),
            description=body.description,
            status=body.status,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        try:
            await self.repo.add(entity)
        except IntegrityError as exc:
            raise AppError("کد ناوگان تکراری است", error_code="duplicate_code", status_code=409) from exc
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="fleet",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id(actor),
            changes=jsonable(body.model_dump()),
        )
        await self.publisher.publish(
            event_type=DeliveryEventType.FLEET_CREATED,
            aggregate_type="fleet",
            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, fleet_id: UUID) -> Fleet:
        entity = await self.repo.get(tenant_id, fleet_id)
        if entity is None:
            raise NotFoundError("ناوگان یافت نشد")
        return entity

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

    async def update(
        self, tenant_id: UUID, fleet_id: UUID, body: FleetUpdate, *, actor: CurrentUser | None = None
    ) -> Fleet:
        entity = await self.get(tenant_id, fleet_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_fleet_name(data["name"])
        for key, value in data.items():
            setattr(entity, key, value)
        entity.version += 1
        entity.updated_by = actor_id(actor)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="fleet",
            entity_id=entity.id,
            action=AuditAction.UPDATE,
            actor_user_id=actor_id(actor),
            changes=jsonable(data),
        )
        await self.publisher.publish(
            event_type=DeliveryEventType.FLEET_UPDATED,
            aggregate_type="fleet",
            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, fleet_id: UUID, *, actor: CurrentUser | None = None
    ) -> Fleet:
        entity = await self.get(tenant_id, fleet_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="fleet",
            entity_id=entity.id,
            action=AuditAction.DELETE,
            actor_user_id=actor_id(actor),
        )
        await self.publisher.publish(
            event_type=DeliveryEventType.FLEET_DELETED,
            aggregate_type="fleet",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class VehicleTypeService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = VehicleTypeRepository(session)
        self.audit = AuditService(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: VehicleTypeCreate, *, actor: CurrentUser | None = None
    ) -> VehicleType:
        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 = VehicleType(
            tenant_id=tenant_id,
            code=code,
            name=validate_vehicle_type_name(body.name),
            description=body.description,
            capacity_kg=body.capacity_kg,
            capacity_volume=body.capacity_volume,
            capability_tags=body.capability_tags,
            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.VEHICLE_TYPE_CREATED,
            aggregate_type="vehicle_type",
            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, type_id: UUID) -> VehicleType:
        entity = await self.repo.get(tenant_id, type_id)
        if entity is None:
            raise NotFoundError("نوع خودرو یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20, spec: VehicleTypeListSpec | None = None
    ) -> tuple[list[VehicleType], 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, type_id: UUID, body: VehicleTypeUpdate, *, actor: CurrentUser | None = None
    ) -> VehicleType:
        entity = await self.get(tenant_id, type_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_vehicle_type_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.VEHICLE_TYPE_UPDATED,
            aggregate_type="vehicle_type",
            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, type_id: UUID, *, actor: CurrentUser | None = None
    ) -> VehicleType:
        entity = await self.get(tenant_id, type_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.publisher.publish(
            event_type=DeliveryEventType.VEHICLE_TYPE_DELETED,
            aggregate_type="vehicle_type",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class VehicleService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.repo = VehicleRepository(session)
        self.fleets = FleetRepository(session)
        self.types = VehicleTypeRepository(session)
        self.assignments = VehicleAssignmentRepository(session)
        self.drivers = DriverRepository(session)
        self.audit = AuditService(session)
        self.publisher = publisher or TransactionalEventPublisher(session)

    async def create(
        self, tenant_id: UUID, body: VehicleCreate, *, actor: CurrentUser | None = None
    ) -> Vehicle:
        if await self.fleets.get(tenant_id, body.fleet_id) is None:
            raise NotFoundError("ناوگان یافت نشد")
        if await self.types.get(tenant_id, body.vehicle_type_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 = Vehicle(
            tenant_id=tenant_id,
            fleet_id=body.fleet_id,
            vehicle_type_id=body.vehicle_type_id,
            code=code,
            plate_number=validate_plate_number(body.plate_number),
            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.VEHICLE_CREATED,
            aggregate_type="vehicle",
            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, vehicle_id: UUID) -> Vehicle:
        entity = await self.repo.get(tenant_id, vehicle_id)
        if entity is None:
            raise NotFoundError("خودرو یافت نشد")
        return entity

    async def list(
        self, tenant_id: UUID, *, offset: int = 0, limit: int = 20, spec: VehicleListSpec | None = None
    ) -> tuple[list[Vehicle], 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, vehicle_id: UUID, body: VehicleUpdate, *, actor: CurrentUser | None = None
    ) -> Vehicle:
        entity = await self.get(tenant_id, vehicle_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "plate_number" in data:
            data["plate_number"] = validate_plate_number(data["plate_number"])
        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.VEHICLE_UPDATED,
            aggregate_type="vehicle",
            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, vehicle_id: UUID, *, actor: CurrentUser | None = None
    ) -> Vehicle:
        entity = await self.get(tenant_id, vehicle_id)
        await self.repo.soft_delete(entity, deleted_by=actor_id(actor))
        await self.publisher.publish(
            event_type=DeliveryEventType.VEHICLE_DELETED,
            aggregate_type="vehicle",
            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 create_assignment(
        self,
        tenant_id: UUID,
        vehicle_id: UUID,
        body: VehicleAssignmentCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> VehicleAssignment:
        await self.get(tenant_id, vehicle_id)
        if await self.drivers.get(tenant_id, body.driver_id) is None:
            raise NotFoundError("راننده یافت نشد")
        active = await self.assignments.get_active_for_vehicle(tenant_id, vehicle_id)
        if active is not None:
            raise AppError(
                "خودرو دارای تخصیص فعال است",
                status_code=409,
                error_code="active_assignment_exists",
            )
        entity = VehicleAssignment(
            tenant_id=tenant_id,
            vehicle_id=vehicle_id,
            driver_id=body.driver_id,
            status=VehicleAssignmentStatus.ACTIVE,
            notes=body.notes,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.assignments.add(entity)
        vehicle = await self.get(tenant_id, vehicle_id)
        vehicle.status = VehicleStatus.ASSIGNED
        vehicle.version += 1
        await self.publisher.publish(
            event_type=DeliveryEventType.VEHICLE_ASSIGNMENT_CREATED,
            aggregate_type="vehicle_assignment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"vehicle_id": str(vehicle_id), "driver_id": str(body.driver_id)},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_assignments(
        self, tenant_id: UUID, vehicle_id: UUID
    ) -> list[VehicleAssignment]:
        await self.get(tenant_id, vehicle_id)
        return list(await self.assignments.list_for_vehicle(tenant_id, vehicle_id))

    async def update_assignment(
        self,
        tenant_id: UUID,
        vehicle_id: UUID,
        assignment_id: UUID,
        body: VehicleAssignmentUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> VehicleAssignment:
        await self.get(tenant_id, vehicle_id)
        entity = await self.assignments.get(tenant_id, assignment_id)
        if entity is None or entity.vehicle_id != vehicle_id:
            raise NotFoundError("تخصیص یافت نشد")
        data = body.model_dump(exclude_unset=True)
        for key, value in data.items():
            setattr(entity, key, value)
        entity.updated_by = actor_id(actor)
        event = DeliveryEventType.VEHICLE_ASSIGNMENT_UPDATED
        if entity.status == VehicleAssignmentStatus.ENDED:
            event = DeliveryEventType.VEHICLE_ASSIGNMENT_ENDED
            vehicle = await self.get(tenant_id, vehicle_id)
            vehicle.status = VehicleStatus.AVAILABLE
            vehicle.version += 1
        await self.publisher.publish(
            event_type=event,
            aggregate_type="vehicle_assignment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"vehicle_id": str(vehicle_id)},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity
