"""
Strategy Application — Scorecard Service.
"""

import uuid
from typing import List, Optional
from uuid import UUID

from ..dtos.strategy_dtos import (
    ScorecardCreateDTO, ScorecardUpdateDTO, ScorecardResponseDTO,
    ScPerspectiveCreateDTO, ScPerspectiveUpdateDTO, ScPerspectiveResponseDTO,
    ScMeasureCreateDTO, ScMeasureUpdateDTO, ScMeasureResponseDTO,
    PerspectiveScoreDTO, ScorecardReportDTO,
)
from ...domain.entities.scorecard import Scorecard
from ...domain.entities.scorecard_perspective import ScorecardPerspective
from ...domain.entities.scorecard_measure import ScorecardMeasure
from ...domain.services.scorecard_calculator import ScorecardCalculator
from ...infrastructure.repositories import (
    DjangoScorecardRepository,
    DjangoScorecardPerspectiveRepository,
    DjangoScorecardMeasureRepository,
)


class ScorecardService:
    """سرویس مدیریت کارت امتیازی متوازن."""

    def __init__(self):
        self.sc_repo = DjangoScorecardRepository()
        self.perspective_repo = DjangoScorecardPerspectiveRepository()
        self.measure_repo = DjangoScorecardMeasureRepository()
        self.calculator = ScorecardCalculator()

    # ─── Scorecard CRUD ───────────────────────────

    def create_scorecard(self, dto: ScorecardCreateDTO) -> ScorecardResponseDTO:
        entity = Scorecard(
            id=uuid.uuid4(),
            tenant_id=dto.tenant_id,
            name=dto.name,
            description=dto.description,
            owner_id=dto.owner_id,
            period_start=dto.period_start,
            period_end=dto.period_end,
            metadata=dto.metadata or {},
        )
        saved = self.sc_repo.save(entity)
        return self._sc_to_response(saved)

    def get_scorecard(self, scorecard_id: UUID) -> Optional[ScorecardResponseDTO]:
        entity = self.sc_repo.find_by_id(scorecard_id)
        if not entity:
            return None
        return self._sc_to_response(entity)

    def update_scorecard(self, scorecard_id: UUID, dto: ScorecardUpdateDTO) -> Optional[ScorecardResponseDTO]:
        entity = self.sc_repo.find_by_id(scorecard_id)
        if not entity:
            return None
        if dto.name is not None:
            entity.name = dto.name
        if dto.description is not None:
            entity.description = dto.description
        if dto.owner_id is not None:
            entity.owner_id = dto.owner_id
        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.metadata is not None:
            entity.metadata = dto.metadata
        saved = self.sc_repo.save(entity)
        return self._sc_to_response(saved)

    def delete_scorecard(self, scorecard_id: UUID) -> bool:
        return self.sc_repo.delete(scorecard_id)

    def activate_scorecard(self, scorecard_id: UUID) -> Optional[ScorecardResponseDTO]:
        entity = self.sc_repo.find_by_id(scorecard_id)
        if not entity:
            return None
        entity.activate()
        saved = self.sc_repo.save(entity)
        return self._sc_to_response(saved)

    def close_scorecard(self, scorecard_id: UUID) -> Optional[ScorecardResponseDTO]:
        entity = self.sc_repo.find_by_id(scorecard_id)
        if not entity:
            return None
        entity.close()
        saved = self.sc_repo.save(entity)
        return self._sc_to_response(saved)

    def archive_scorecard(self, scorecard_id: UUID) -> Optional[ScorecardResponseDTO]:
        entity = self.sc_repo.find_by_id(scorecard_id)
        if not entity:
            return None
        entity.archive()
        saved = self.sc_repo.save(entity)
        return self._sc_to_response(saved)

    # ─── Perspective CRUD ─────────────────────────

    def create_perspective(self, dto: ScPerspectiveCreateDTO) -> ScPerspectiveResponseDTO:
        entity = ScorecardPerspective(
            id=uuid.uuid4(),
            tenant_id=dto.tenant_id,
            scorecard_id=dto.scorecard_id,
            name=dto.name,
            weight=dto.weight,
            sort_order=dto.sort_order,
            metadata=dto.metadata or {},
        )
        saved = self.perspective_repo.save(entity)
        return self._perspective_to_response(saved)

    def update_perspective(self, perspective_id: UUID, dto: ScPerspectiveUpdateDTO) -> Optional[ScPerspectiveResponseDTO]:
        entity = self.perspective_repo.find_by_id(perspective_id)
        if not entity:
            return None
        if dto.name is not None:
            entity.name = dto.name
        if dto.weight is not None:
            entity.weight = dto.weight
        if dto.sort_order is not None:
            entity.sort_order = dto.sort_order
        if dto.metadata is not None:
            entity.metadata = dto.metadata
        saved = self.perspective_repo.save(entity)
        return self._perspective_to_response(saved)

    def delete_perspective(self, perspective_id: UUID) -> bool:
        return self.perspective_repo.delete(perspective_id)

    def list_perspectives(self, scorecard_id: UUID) -> List[ScPerspectiveResponseDTO]:
        entities = self.perspective_repo.find_by_scorecard(scorecard_id)
        return [self._perspective_to_response(e) for e in entities]

    # ─── Measure CRUD ─────────────────────────────

    def create_measure(self, dto: ScMeasureCreateDTO) -> ScMeasureResponseDTO:
        entity = ScorecardMeasure(
            id=uuid.uuid4(),
            tenant_id=dto.tenant_id,
            perspective_id=dto.perspective_id,
            kpi_assignment_id=dto.kpi_assignment_id,
            weight=dto.weight,
            target_value=dto.target_value,
            actual_value=dto.actual_value,
            metadata=dto.metadata or {},
        )
        entity.calculate_score()
        entity.update_status()
        saved = self.measure_repo.save(entity)
        return self._measure_to_response(saved)

    def update_measure(self, measure_id: UUID, dto: ScMeasureUpdateDTO) -> Optional[ScMeasureResponseDTO]:
        entity = self.measure_repo.find_by_id(measure_id)
        if not entity:
            return None
        if dto.kpi_assignment_id is not None:
            entity.kpi_assignment_id = dto.kpi_assignment_id
        if dto.weight is not None:
            entity.weight = dto.weight
        if dto.target_value is not None:
            entity.target_value = dto.target_value
        if dto.actual_value is not None:
            entity.actual_value = dto.actual_value
        if dto.metadata is not None:
            entity.metadata = dto.metadata
        entity.calculate_score()
        entity.update_status()
        saved = self.measure_repo.save(entity)
        return self._measure_to_response(saved)

    def delete_measure(self, measure_id: UUID) -> bool:
        return self.measure_repo.delete(measure_id)

    def list_measures(self, perspective_id: UUID) -> List[ScMeasureResponseDTO]:
        entities = self.measure_repo.find_by_perspective(perspective_id)
        return [self._measure_to_response(e) for e in entities]

    # ─── Report ───────────────────────────────────

    def generate_report(self, scorecard_id: UUID) -> Optional[ScorecardReportDTO]:
        scorecard = self.sc_repo.find_by_id(scorecard_id)
        if not scorecard:
            return None

        perspectives = self.perspective_repo.find_by_scorecard(scorecard_id)
        measures_map = {}
        for p in perspectives:
            measures_map[p.id] = self.measure_repo.find_by_perspective(p.id)

        report = self.calculator.generate_report(scorecard, perspectives, measures_map)

        return ScorecardReportDTO(
            scorecard_id=report.scorecard_id,
            overall_score=report.overall_score,
            perspectives=[
                PerspectiveScoreDTO(
                    perspective_id=ps.perspective_id,
                    name=ps.name,
                    weight=ps.weight,
                    score=ps.score,
                    weighted_score=ps.weighted_score,
                    measure_count=ps.measure_count,
                ) for ps in report.perspectives
            ],
        )

    # ─── Mappers ──────────────────────────────────

    @staticmethod
    def _sc_to_response(e: Scorecard) -> ScorecardResponseDTO:
        return ScorecardResponseDTO(
            id=e.id, tenant_id=e.tenant_id,
            name=e.name, description=e.description,
            owner_id=e.owner_id,
            period_start=e.period_start, period_end=e.period_end,
            status=e.status, metadata=e.metadata,
            created_at=e.created_at, updated_at=e.updated_at,
        )

    @staticmethod
    def _perspective_to_response(e: ScorecardPerspective) -> ScPerspectiveResponseDTO:
        return ScPerspectiveResponseDTO(
            id=e.id, tenant_id=e.tenant_id,
            scorecard_id=e.scorecard_id, name=e.name,
            weight=e.weight, sort_order=e.sort_order,
            metadata=e.metadata,
            created_at=e.created_at, updated_at=e.updated_at,
        )

    @staticmethod
    def _measure_to_response(e: ScorecardMeasure) -> ScMeasureResponseDTO:
        return ScMeasureResponseDTO(
            id=e.id, tenant_id=e.tenant_id,
            perspective_id=e.perspective_id,
            kpi_assignment_id=e.kpi_assignment_id,
            weight=e.weight, target_value=e.target_value,
            actual_value=e.actual_value, score=e.score,
            status=e.status, metadata=e.metadata,
            created_at=e.created_at, updated_at=e.updated_at,
        )
