"""
Strategy Application — OKR Service.

سرویس اپلیکیشن برای مدیریت اهداف، نتایج کلیدی و ثبت پیشرفت.
"""

from decimal import Decimal
from typing import List, Optional
from uuid import UUID

from apps.core.event_bus.events import event_bus

from ..dtos.strategy_dtos import (
    ObjectiveCreateDTO, ObjectiveUpdateDTO, ObjectiveResponseDTO,
    KeyResultCreateDTO, KeyResultUpdateDTO, KeyResultResponseDTO,
    CheckInCreateDTO, CheckInResponseDTO,
    ObjectiveProgressDTO, CascadeProgressDTO,
)
from ...domain.entities.objective import Objective
from ...domain.entities.key_result import KeyResult
from ...domain.entities.key_result_checkin import KeyResultCheckIn
from ...domain.services.okr_calculator import OKRCalculator
from ...domain.events.strategy_events import (
    ObjectiveCreated, ObjectiveActivated, ObjectiveAchieved, ObjectiveMissed,
    KeyResultCreated, KeyResultUpdated, KeyResultAchieved,
    CheckInRecorded,
)
from ...infrastructure.repositories.objective_repo import DjangoObjectiveRepository
from ...infrastructure.repositories.key_result_repo import DjangoKeyResultRepository
from ...infrastructure.repositories.key_result_checkin_repo import DjangoKeyResultCheckInRepository


class OKRService:
    """سرویس مدیریت OKR (Objectives & Key Results)."""

    def __init__(self):
        self.objective_repo = DjangoObjectiveRepository()
        self.kr_repo = DjangoKeyResultRepository()
        self.checkin_repo = DjangoKeyResultCheckInRepository()
        self.calculator = OKRCalculator()

    # ═══════════════════════════════════════════════
    # Objectives
    # ═══════════════════════════════════════════════

    def create_objective(self, dto: ObjectiveCreateDTO) -> ObjectiveResponseDTO:
        entity = Objective(
            tenant_id=dto.tenant_id,
            theme_id=dto.theme_id,
            parent_id=dto.parent_id,
            title=dto.title,
            description=dto.description,
            level=dto.level,
            time_frame=dto.time_frame,
            period_start=dto.period_start,
            period_end=dto.period_end,
            weight=dto.weight,
            owner_id=dto.owner_id,
            tags=dto.tags or [],
            metadata=dto.metadata or {},
        )
        saved = self.objective_repo.save(entity)

        event_bus.publish(ObjectiveCreated(
            tenant_id=saved.tenant_id,
            objective_id=saved.id,
            theme_id=saved.theme_id,
            title=saved.title,
        ))

        return self._obj_to_response(saved)

    def get_objective(self, objective_id: UUID) -> Optional[ObjectiveResponseDTO]:
        entity = self.objective_repo.find_by_id(objective_id)
        return self._obj_to_response(entity) if entity else None

    def list_objectives(self, tenant_id: UUID) -> List[ObjectiveResponseDTO]:
        entities = self.objective_repo.find_by_tenant(tenant_id)
        return [self._obj_to_response(e) for e in entities]

    def list_by_theme(self, theme_id: UUID) -> List[ObjectiveResponseDTO]:
        entities = self.objective_repo.find_by_theme(theme_id)
        return [self._obj_to_response(e) for e in entities]

    def list_children(self, parent_id: UUID) -> List[ObjectiveResponseDTO]:
        entities = self.objective_repo.find_children(parent_id)
        return [self._obj_to_response(e) for e in entities]

    def update_objective(self, objective_id: UUID, dto: ObjectiveUpdateDTO) -> Optional[ObjectiveResponseDTO]:
        entity = self.objective_repo.find_by_id(objective_id)
        if not entity:
            return None

        if dto.title is not None:
            entity.title = dto.title
        if dto.description is not None:
            entity.description = dto.description
        if dto.theme_id is not None:
            entity.theme_id = dto.theme_id
        if dto.parent_id is not None:
            entity.parent_id = dto.parent_id
        if dto.level is not None:
            entity.level = dto.level
        if dto.time_frame is not None:
            entity.time_frame = dto.time_frame
        if dto.period_start is not None:
            entity.period_start = dto.period_start
        if dto.period_end is not None:
            entity.period_end = dto.period_end
        if dto.weight is not None:
            entity.weight = dto.weight
        if dto.owner_id is not None:
            entity.owner_id = dto.owner_id
        if dto.tags is not None:
            entity.tags = dto.tags
        if dto.metadata is not None:
            entity.metadata = dto.metadata

        saved = self.objective_repo.save(entity)
        return self._obj_to_response(saved)

    def delete_objective(self, objective_id: UUID) -> bool:
        return self.objective_repo.delete(objective_id)

    def activate_objective(self, objective_id: UUID) -> Optional[ObjectiveResponseDTO]:
        entity = self.objective_repo.find_by_id(objective_id)
        if not entity:
            return None
        entity.activate()
        saved = self.objective_repo.save(entity)
        event_bus.publish(ObjectiveActivated(tenant_id=saved.tenant_id, objective_id=saved.id))
        return self._obj_to_response(saved)

    def achieve_objective(self, objective_id: UUID) -> Optional[ObjectiveResponseDTO]:
        entity = self.objective_repo.find_by_id(objective_id)
        if not entity:
            return None
        entity.achieve()
        saved = self.objective_repo.save(entity)
        event_bus.publish(ObjectiveAchieved(tenant_id=saved.tenant_id, objective_id=saved.id))
        return self._obj_to_response(saved)

    def miss_objective(self, objective_id: UUID) -> Optional[ObjectiveResponseDTO]:
        entity = self.objective_repo.find_by_id(objective_id)
        if not entity:
            return None
        entity.miss()
        saved = self.objective_repo.save(entity)
        event_bus.publish(ObjectiveMissed(tenant_id=saved.tenant_id, objective_id=saved.id))
        return self._obj_to_response(saved)

    # ═══════════════════════════════════════════════
    # Key Results
    # ═══════════════════════════════════════════════

    def create_key_result(self, dto: KeyResultCreateDTO) -> KeyResultResponseDTO:
        entity = KeyResult(
            tenant_id=dto.tenant_id,
            objective_id=dto.objective_id,
            title=dto.title,
            metric_type=dto.metric_type,
            start_value=dto.start_value,
            target_value=dto.target_value,
            weight=dto.weight,
            owner_id=dto.owner_id,
            due_date=dto.due_date,
            metadata=dto.metadata or {},
        )
        saved = self.kr_repo.save(entity)

        event_bus.publish(KeyResultCreated(
            tenant_id=saved.tenant_id,
            key_result_id=saved.id,
            objective_id=saved.objective_id,
            title=saved.title,
        ))

        return self._kr_to_response(saved)

    def get_key_result(self, key_result_id: UUID) -> Optional[KeyResultResponseDTO]:
        entity = self.kr_repo.find_by_id(key_result_id)
        return self._kr_to_response(entity) if entity else None

    def list_key_results(self, objective_id: UUID) -> List[KeyResultResponseDTO]:
        entities = self.kr_repo.find_by_objective(objective_id)
        return [self._kr_to_response(e) for e in entities]

    def update_key_result(self, key_result_id: UUID, dto: KeyResultUpdateDTO) -> Optional[KeyResultResponseDTO]:
        entity = self.kr_repo.find_by_id(key_result_id)
        if not entity:
            return None

        if dto.title is not None:
            entity.title = dto.title
        if dto.metric_type is not None:
            entity.metric_type = dto.metric_type
        if dto.start_value is not None:
            entity.start_value = dto.start_value
        if dto.target_value is not None:
            entity.target_value = dto.target_value
        if dto.current_value is not None:
            entity.update_value(dto.current_value)
        if dto.weight is not None:
            entity.weight = dto.weight
        if dto.owner_id is not None:
            entity.owner_id = dto.owner_id
        if dto.due_date is not None:
            entity.due_date = dto.due_date
        if dto.metadata is not None:
            entity.metadata = dto.metadata

        saved = self.kr_repo.save(entity)

        event_bus.publish(KeyResultUpdated(
            tenant_id=saved.tenant_id,
            key_result_id=saved.id,
            current_value=str(saved.current_value),
            progress=str(saved.progress),
        ))

        return self._kr_to_response(saved)

    def delete_key_result(self, key_result_id: UUID) -> bool:
        return self.kr_repo.delete(key_result_id)

    # ═══════════════════════════════════════════════
    # Check-Ins
    # ═══════════════════════════════════════════════

    def record_check_in(self, dto: CheckInCreateDTO) -> CheckInResponseDTO:
        """ثبت پیشرفت و بروزرسانی خودکار مقدار نتیجه کلیدی."""
        entity = KeyResultCheckIn(
            tenant_id=dto.tenant_id,
            key_result_id=dto.key_result_id,
            value=dto.value,
            confidence=dto.confidence,
            notes=dto.notes,
            checked_by=dto.checked_by,
            metadata=dto.metadata or {},
        )
        saved_checkin = self.checkin_repo.save(entity)

        # Update key result current value
        kr = self.kr_repo.find_by_id(dto.key_result_id)
        if kr:
            kr.update_value(dto.value)
            self.kr_repo.save(kr)

            # Auto-update objective progress
            self._recalculate_objective_progress(kr.objective_id)

        event_bus.publish(CheckInRecorded(
            tenant_id=saved_checkin.tenant_id,
            check_in_id=saved_checkin.id,
            key_result_id=saved_checkin.key_result_id,
            value=str(saved_checkin.value),
            confidence=saved_checkin.confidence,
        ))

        return self._checkin_to_response(saved_checkin)

    def list_check_ins(self, key_result_id: UUID) -> List[CheckInResponseDTO]:
        entities = self.checkin_repo.find_by_key_result(key_result_id)
        return [self._checkin_to_response(e) for e in entities]

    # ═══════════════════════════════════════════════
    # OKR Analysis
    # ═══════════════════════════════════════════════

    def calculate_progress(self, objective_id: UUID) -> Optional[ObjectiveProgressDTO]:
        """محاسبه پیشرفت یک هدف."""
        objective = self.objective_repo.find_by_id(objective_id)
        if not objective:
            return None

        key_results = self.kr_repo.find_by_objective(objective_id)
        result = self.calculator.calculate_objective_progress(objective, key_results)

        return ObjectiveProgressDTO(
            objective_id=result.objective_id,
            weighted_progress=result.weighted_progress,
            key_results_count=result.key_results_count,
            completed_count=result.completed_count,
            at_risk_count=result.at_risk_count,
        )

    def calculate_cascade(self, objective_id: UUID) -> Optional[CascadeProgressDTO]:
        """محاسبه پیشرفت آبشاری هدف و فرزندان."""
        objective = self.objective_repo.find_by_id(objective_id)
        if not objective:
            return None

        own_krs = self.kr_repo.find_by_objective(objective_id)
        children = self.objective_repo.find_children(objective_id)

        children_krs = {}
        for child in children:
            children_krs[child.id] = self.kr_repo.find_by_objective(child.id)

        result = self.calculator.calculate_cascade_progress(
            objective, own_krs, children, children_krs,
        )

        return CascadeProgressDTO(
            objective_id=result.objective_id,
            own_progress=result.own_progress,
            children_progress=result.children_progress,
            combined_progress=result.combined_progress,
            children_count=result.children_count,
        )

    # ═══════════════════════════════════════════════
    # Private Helpers
    # ═══════════════════════════════════════════════

    def _recalculate_objective_progress(self, objective_id: UUID) -> None:
        """محاسبه مجدد و ذخیره پیشرفت هدف."""
        if not objective_id:
            return
        objective = self.objective_repo.find_by_id(objective_id)
        if not objective:
            return
        key_results = self.kr_repo.find_by_objective(objective_id)
        result = self.calculator.calculate_objective_progress(objective, key_results)
        objective.update_progress(result.weighted_progress)
        self.objective_repo.save(objective)

    @staticmethod
    def _obj_to_response(entity: Objective) -> ObjectiveResponseDTO:
        return ObjectiveResponseDTO(
            id=entity.id,
            tenant_id=entity.tenant_id,
            title=entity.title,
            description=entity.description,
            theme_id=entity.theme_id,
            parent_id=entity.parent_id,
            level=entity.level,
            time_frame=entity.time_frame,
            period_start=entity.period_start,
            period_end=entity.period_end,
            status=entity.status,
            weight=entity.weight,
            progress=entity.progress,
            owner_id=entity.owner_id,
            tags=entity.tags or [],
            metadata=entity.metadata or {},
            created_at=entity.created_at,
            updated_at=entity.updated_at,
        )

    @staticmethod
    def _kr_to_response(entity: KeyResult) -> KeyResultResponseDTO:
        return KeyResultResponseDTO(
            id=entity.id,
            tenant_id=entity.tenant_id,
            objective_id=entity.objective_id,
            title=entity.title,
            metric_type=entity.metric_type,
            start_value=entity.start_value,
            target_value=entity.target_value,
            current_value=entity.current_value,
            weight=entity.weight,
            status=entity.status,
            owner_id=entity.owner_id,
            due_date=entity.due_date,
            progress=entity.progress,
            metadata=entity.metadata or {},
            created_at=entity.created_at,
            updated_at=entity.updated_at,
        )

    @staticmethod
    def _checkin_to_response(entity: KeyResultCheckIn) -> CheckInResponseDTO:
        return CheckInResponseDTO(
            id=entity.id,
            tenant_id=entity.tenant_id,
            key_result_id=entity.key_result_id,
            value=entity.value,
            confidence=entity.confidence,
            notes=entity.notes,
            checked_by=entity.checked_by,
            check_date=entity.check_date,
            metadata=entity.metadata or {},
            created_at=entity.created_at,
            updated_at=entity.updated_at,
        )
