"""Training Management services — Phase 9.6."""
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 SportsCenterEventType
from app.models.training import (
    Assessment,
    BodyComposition,
    Exercise,
    Measurement,
    NutritionPlan,
    PerformanceRecord,
    ProgressRecord,
    TrainingGoal,
    TrainingProgram,
    WorkoutAssignment,
    WorkoutPlan,
)
from app.models.types import AuditAction
from app.repositories.foundation import SportsCenterRepository
from app.repositories.members import MemberRepository
from app.repositories.training import (
    AssessmentRepository,
    BodyCompositionRepository,
    ExerciseRepository,
    MeasurementRepository,
    NutritionPlanRepository,
    PerformanceRecordRepository,
    ProgressRecordRepository,
    TrainingGoalRepository,
    TrainingProgramRepository,
    WorkoutAssignmentRepository,
    WorkoutPlanRepository,
)
from app.schemas.training import (
    AssessmentCreate,
    BodyCompositionCreate,
    ExerciseCreate,
    ExerciseUpdate,
    MeasurementCreate,
    NutritionPlanCreate,
    PerformanceRecordCreate,
    ProgressRecordCreate,
    TrainingGoalCreate,
    TrainingProgramCreate,
    TrainingProgramUpdate,
    WorkoutAssignmentCreate,
    WorkoutPlanCreate,
    WorkoutPlanUpdate,
)
from app.services.audit_service import AuditService
from app.validators import ensure_optimistic_version, validate_code, validate_non_empty
from shared.exceptions import NotFoundError
from shared.security import CurrentUser


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


class _TrainingBase:
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        self.session = session
        self.audit = AuditService(session)
        self.publisher = publisher or get_event_publisher()
        self.centers = SportsCenterRepository(session)
        self.members = MemberRepository(session)

    async def _require_center(self, tenant_id: UUID, sports_center_id: UUID):
        center = await self.centers.get(tenant_id, sports_center_id)
        if center is None:
            raise NotFoundError("مرکز ورزشی یافت نشد", error_code="sports_center_not_found")
        return center

    async def _require_member(self, tenant_id: UUID, member_id: UUID):
        member = await self.members.get(tenant_id, member_id)
        if member is None:
            raise NotFoundError("عضو یافت نشد", error_code="member_not_found")
        return member


class TrainingProgramService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = TrainingProgramRepository(session)

    async def create(self, tenant_id: UUID, body: TrainingProgramCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        validate_code(body.code)
        validate_non_empty(body.name, field="name")
        entity = TrainingProgram(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            code=body.code,
            name=body.name,
            description=body.description,
            sport_id=body.sport_id,
            coach_id=body.coach_id,
            status=body.status,
            duration_weeks=body.duration_weeks,
            metadata_json=body.metadata_json,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.TRAINING_PROGRAM_CREATED,
            aggregate_type="training_program",
            aggregate_id=entity.id,
            payload={"code": body.code},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("برنامه تمرینی یافت نشد", error_code="training_program_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: TrainingProgramUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        ensure_optimistic_version(entity, body.version)
        _apply_update(entity, body.model_dump(exclude_unset=True, exclude={"version"}))
        entity.version += 1
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class ExerciseService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = ExerciseRepository(session)

    async def create(self, tenant_id: UUID, body: ExerciseCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        validate_code(body.code)
        entity = Exercise(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            code=body.code,
            name=body.name,
            description=body.description,
            muscle_group=body.muscle_group,
            equipment_ref=body.equipment_ref,
            default_sets=body.default_sets,
            default_reps=body.default_reps,
            default_duration_seconds=body.default_duration_seconds,
            metadata_json=body.metadata_json,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("تمرین یافت نشد", error_code="exercise_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: ExerciseUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        _apply_update(entity, body.model_dump(exclude_unset=True))
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class WorkoutPlanService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = WorkoutPlanRepository(session)

    async def create(self, tenant_id: UUID, body: WorkoutPlanCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        validate_code(body.code)
        entity = WorkoutPlan(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            program_id=body.program_id,
            code=body.code,
            name=body.name,
            description=body.description,
            coach_id=body.coach_id,
            status=body.status,
            exercises=body.exercises,
            metadata_json=body.metadata_json,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.WORKOUT_PLAN_CREATED,
            aggregate_type="workout_plan",
            aggregate_id=entity.id,
            payload={"code": body.code},
        )
        return entity

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

    async def get(self, tenant_id: UUID, entity_id: UUID):
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("برنامه تمرین یافت نشد", error_code="workout_plan_not_found")
        return entity

    async def update(self, tenant_id: UUID, entity_id: UUID, body: WorkoutPlanUpdate, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        ensure_optimistic_version(entity, body.version)
        _apply_update(entity, body.model_dump(exclude_unset=True, exclude={"version"}))
        entity.version += 1
        entity.updated_by = actor.user_id if actor else None
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def delete(self, tenant_id: UUID, entity_id: UUID, *, actor: CurrentUser):
        entity = await self.get(tenant_id, entity_id)
        await self.repo.soft_delete(entity, deleted_by=actor.user_id if actor else None)
        await self.session.commit()
        return entity


class WorkoutAssignmentService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = WorkoutAssignmentRepository(session)
        self.plans = WorkoutPlanRepository(session)

    async def create(self, tenant_id: UUID, body: WorkoutAssignmentCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        plan = await self.plans.get(tenant_id, body.workout_plan_id)
        if plan is None:
            raise NotFoundError("برنامه تمرین یافت نشد", error_code="workout_plan_not_found")
        entity = WorkoutAssignment(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            workout_plan_id=body.workout_plan_id,
            member_id=body.member_id,
            coach_id=body.coach_id,
            starts_on=body.starts_on,
            ends_on=body.ends_on,
            status=body.status,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.WORKOUT_ASSIGNED,
            aggregate_type="workout_assignment",
            aggregate_id=entity.id,
            payload={"member_id": str(body.member_id)},
        )
        return entity

    async def list_by_member(self, tenant_id: UUID, member_id: UUID, *, offset=0, limit=20):
        return await self.repo.list_by_member(tenant_id, member_id, offset=offset, limit=limit)


class TrainingGoalService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = TrainingGoalRepository(session)

    async def create(self, tenant_id: UUID, body: TrainingGoalCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = TrainingGoal(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            program_id=body.program_id,
            title=body.title,
            description=body.description,
            target_value=body.target_value,
            current_value=body.current_value,
            unit=body.unit,
            target_date=body.target_date,
            status=body.status,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.TRAINING_GOAL_CREATED,
            aggregate_type="training_goal",
            aggregate_id=entity.id,
            payload={"member_id": str(body.member_id)},
        )
        return entity

    async def list_by_member(self, tenant_id: UUID, member_id: UUID, *, offset=0, limit=20):
        return await self.repo.list_by_member(tenant_id, member_id, offset=offset, limit=limit)


class MeasurementService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = MeasurementRepository(session)

    async def create(self, tenant_id: UUID, body: MeasurementCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = Measurement(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            metric_code=body.metric_code,
            metric_name=body.metric_name,
            value=body.value,
            unit=body.unit,
            measured_at=body.measured_at,
            recorded_by_coach_id=body.recorded_by_coach_id,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.MEASUREMENT_RECORDED,
            aggregate_type="measurement",
            aggregate_id=entity.id,
            payload={"metric_code": body.metric_code},
        )
        return entity

    async def list_by_member(self, tenant_id: UUID, member_id: UUID, *, offset=0, limit=20):
        return await self.repo.list_by_member(tenant_id, member_id, offset=offset, limit=limit)


class ProgressRecordService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = ProgressRecordRepository(session)

    async def create(self, tenant_id: UUID, body: ProgressRecordCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = ProgressRecord(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            goal_id=body.goal_id,
            workout_assignment_id=body.workout_assignment_id,
            recorded_at=body.recorded_at,
            summary=body.summary,
            metrics=body.metrics,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.PROGRESS_RECORDED,
            aggregate_type="progress_record",
            aggregate_id=entity.id,
            payload={"member_id": str(body.member_id)},
        )
        return entity


class NutritionPlanService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = NutritionPlanRepository(session)

    async def create(self, tenant_id: UUID, body: NutritionPlanCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = NutritionPlan(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            coach_id=body.coach_id,
            title=body.title,
            description=body.description,
            starts_on=body.starts_on,
            ends_on=body.ends_on,
            daily_targets=body.daily_targets,
            is_active=body.is_active,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_by_member(self, tenant_id: UUID, member_id: UUID, *, offset=0, limit=20):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)


class AssessmentService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = AssessmentRepository(session)

    async def create(self, tenant_id: UUID, body: AssessmentCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = Assessment(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            coach_id=body.coach_id,
            assessment_kind=body.assessment_kind,
            title=body.title,
            assessed_at=body.assessed_at,
            score=body.score,
            results=body.results,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            tenant_id=tenant_id,
            event_type=SportsCenterEventType.ASSESSMENT_COMPLETED,
            aggregate_type="assessment",
            aggregate_id=entity.id,
            payload={"assessment_kind": body.assessment_kind.value},
        )
        return entity


class PerformanceRecordService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = PerformanceRecordRepository(session)

    async def create(self, tenant_id: UUID, body: PerformanceRecordCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = PerformanceRecord(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            exercise_id=body.exercise_id,
            recorded_at=body.recorded_at,
            value=body.value,
            unit=body.unit,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity


class BodyCompositionService(_TrainingBase):
    def __init__(self, session: AsyncSession, publisher=None) -> None:
        super().__init__(session, publisher)
        self.repo = BodyCompositionRepository(session)

    async def create(self, tenant_id: UUID, body: BodyCompositionCreate, *, actor: CurrentUser):
        await self._require_center(tenant_id, body.sports_center_id)
        await self._require_member(tenant_id, body.member_id)
        entity = BodyComposition(
            tenant_id=tenant_id,
            sports_center_id=body.sports_center_id,
            member_id=body.member_id,
            measured_at=body.measured_at,
            weight_kg=body.weight_kg,
            body_fat_percent=body.body_fat_percent,
            muscle_mass_kg=body.muscle_mass_kg,
            bmi=body.bmi,
            metrics=body.metrics,
            notes=body.notes,
            created_by=actor.user_id if actor else None,
            updated_by=actor.user_id if actor else None,
        )
        await self.repo.add(entity)
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_by_member(self, tenant_id: UUID, member_id: UUID, *, offset=0, limit=20):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)
