from __future__ import annotations

import logging
from pathlib import Path

from app.models.schemas import (
    DossierCoverageResponse,
    RemediationBuildRequest,
    RemediationBuildResponse,
    RemediationGapsResponse,
    RemediationListResponse,
)

from .service_coverage_helpers import ServiceCoverageHelpersMixin
from .service_delegates import ServiceDelegatesMixin
from .service_helpers import ServiceHelpersMixin

logger = logging.getLogger("dscons.remediation.planning")


class RemediationPlanningService(
    ServiceHelpersMixin,
    ServiceCoverageHelpersMixin,
    ServiceDelegatesMixin,
):
    """Dịch vụ lập kế hoạch khắc phục và bù đắp hồ sơ công trình."""

    def __init__(
        self,
        postgres_client: PostgresClient | None = None,
        manifest_path: str | Path = "tmp/hd2026_project_coverage_manifest.json",
    ) -> None:
        self._postgres_client = postgres_client or PostgresClient()
        self._manifest_path = Path(manifest_path)

    def list_plans(
        self,
        project_code: str | None = None,
        review_id: str | None = None,
        finding_code: str | None = None,
    ) -> RemediationListResponse:
        """Trả danh sách remediation plan."""
        sessions, source, used_fallback, message = self._load_sessions(
            project_code=project_code
        )
        coverage_index, coverage_used_fallback, coverage_message = (
            self._load_coverage_index_with_status()
        )
        items = []
        for session in sessions:
            if review_id and str(session.get("review_id") or "") != review_id:
                continue
            for finding in self._ensure_list(session.get("findings")):
                if (
                    finding_code
                    and str(finding.get("finding_code") or "") != finding_code
                ):
                    continue
                plan = self._build_plan_from_finding(
                    session=session,
                    finding=finding,
                    source=source,
                    source_used_fallback=used_fallback,
                    coverage_used_fallback=coverage_used_fallback,
                    coverage_message=coverage_message,
                    coverage_project=coverage_index.get(
                        str(session.get("project_code") or "")
                    ),
                )
                if plan is not None:
                    items.append(plan)
        response_message = message
        if not items and not used_fallback and any(sessions):
            response_message = "Không tìm thấy open findings có document_type phù hợp để lập remediation plan."
        elif not items and not any(sessions):
            response_message = (
                message
                or "Chưa có dossier review sessions để suy diễn remediation plan."
            )
        return RemediationListResponse(
            items=items,
            source=source,
            used_fallback=used_fallback or (coverage_used_fallback and bool(items)),
            message=self._merge_messages(
                response_message, coverage_message if bool(items) else None
            ),
        )

    def build_plan(self, payload: RemediationBuildRequest) -> RemediationBuildResponse:
        """Build một plan cụ thể."""
        sessions, source, used_fallback, message = self._load_sessions(
            project_code=payload.project_code
        )
        coverage_index, coverage_used_fallback, coverage_message = (
            self._load_coverage_index_with_status()
        )
        for session in sessions:
            if (
                payload.review_session_id
                and str(session.get("review_id") or "") != payload.review_session_id
            ):
                continue
            for finding in self._ensure_list(session.get("findings")):
                if not self._matches_build_request(finding=finding, payload=payload):
                    continue
                plan = self._build_plan_from_finding(
                    session=session,
                    finding=finding,
                    source=source,
                    source_used_fallback=used_fallback,
                    coverage_used_fallback=coverage_used_fallback,
                    coverage_message=coverage_message,
                    coverage_project=coverage_index.get(
                        str(session.get("project_code") or "")
                    ),
                )
                if plan is None:
                    continue
                return RemediationBuildResponse(
                    item=plan,
                    source=source,
                    used_fallback=used_fallback or coverage_used_fallback,
                    message=self._merge_messages(message, coverage_message),
                )
        return RemediationBuildResponse(
            item=None,
            source=source,
            used_fallback=used_fallback or coverage_used_fallback,
            message=self._merge_messages(
                message or "Không tìm thấy finding phù hợp để build remediation plan.",
                coverage_message,
            ),
        )

    def list_gaps(
        self, project_code: str | None = None, review_id: str | None = None
    ) -> RemediationGapsResponse:
        """Liệt kê gap chuẩn hóa."""
        plans_response = self.list_plans(project_code=project_code, review_id=review_id)
        items = [self._build_gap_item(plan) for plan in plans_response.items]
        message = plans_response.message
        if not items and not plans_response.used_fallback:
            message = message or "Không có remediation gaps đang mở."
        return RemediationGapsResponse(
            items=items,
            source=plans_response.source,
            used_fallback=plans_response.used_fallback,
            message=message,
        )

    def get_project_coverage(self, project_code: str) -> DossierCoverageResponse:
        """Đọc coverage của project."""
        try:
            manifest = self._load_coverage_manifest()
        except ValueError as exc:
            return self._build_fallback_project_coverage_response(
                project_code=project_code,
                message=f"Không thể nạp coverage manifest; trả fallback coverage rỗng an toàn. Chi tiết: {exc}",
            )
        project_rows = self._ensure_list(manifest.get("projects"))
        matched = next(
            (
                item
                for item in project_rows
                if str(item.get("project_code") or "").strip().lower()
                == project_code.strip().lower()
            ),
            None,
        )
        if matched is None:
            return self._build_fallback_project_coverage_response(
                project_code=project_code,
                message=f"Không tìm thấy coverage manifest cho project_code={project_code}; trả fallback coverage rỗng an toàn.",
            )
        return DossierCoverageResponse(
            project_code=str(matched.get("project_code") or project_code),
            ingest_batch=str(manifest.get("ingest_batch") or "") or None,
            manifest_path=str(self._manifest_path),
            generated_at=str(manifest.get("generated_at") or "") or None,
            project=self._coverage_project_payload(matched),
            source="manifest",
            used_fallback=False,
            message="Coverage manifest được nạp từ kết quả ingest HD-2026.",
        )
