#!/usr/bin/env python3
"""Export errors for Claude integration use case.

Implements the SPEC-CLAUDE-002 compatibility layer so existing tests interact with
the application service without regressions.
"""

from __future__ import annotations

import inspect
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from datetime import datetime
from typing import Any

from noveler.domain.entities.claude_integration_session import ClaudeIntegrationSession
from noveler.domain.entities.file_quality_check_session import (
    CheckResult,
    CheckStatus,
    CheckType,
    FileQualityCheckSession,
)
from noveler.domain.repositories.file_quality_check_session_repository import FileQualityCheckSessionRepository
from noveler.domain.services.claude_code_format_service import ClaudeCodeFormatService
from noveler.domain.value_objects.error_export_format import ErrorExportFormat
from noveler.domain.value_objects.file_path import FilePath
from noveler.domain.value_objects.project_time import project_now


@dataclass(frozen=True)
class ExportErrorsRequest:
    """Request payload that controls how errors are exported for Claude.

    Attributes:
        target_file: Optional path whose errors should be prioritised.
        format_config: Explicit format instructions overriding defaults.
        priority_filter: Optional priority level used to filter issues.
    """

    target_file: FilePath | None = None
    format_config: ErrorExportFormat | None = None
    priority_filter: str | None = None


class ExportErrorsForClaudeUseCase:
    """Coordinate error exports tailored for Claude while preserving legacy API semantics."""

    def __init__(
        self,
        session_repository: FileQualityCheckSessionRepository,
        format_service: ClaudeCodeFormatService,
        claude_adapter: Any | None = None,
        *,
        default_format: ErrorExportFormat | None = None,
        logger_service: Any | None = None,
    ) -> None:
        """Initialise the use case with repository, formatting, and adapters.

        Args:
            session_repository: Repository that provides quality check sessions.
            format_service: Service responsible for formatting errors.
            claude_adapter: Optional adapter performing side effects for Claude.
            default_format: Default export format when none is provided.
            logger_service: Optional logger used for diagnostics.
        """
        self._session_repository = session_repository
        self._format_service = format_service
        self._claude_adapter = claude_adapter
        self._default_format = default_format or ErrorExportFormat.markdown_format()
        self._logger = logger_service

    async def execute_export(
        self,
        *,
        format_config: ErrorExportFormat | None = None,
        priority_filter: str | None = None,
        limit: int = 10,
    ) -> ClaudeIntegrationSession:
        """Export errors from the most recent quality sessions.

        Args:
            format_config: Optional explicit export format.
            priority_filter: Priority level filter applied to issues.
            limit: Maximum number of recent sessions to consider.

        Returns:
            ClaudeIntegrationSession: Session containing the export payload.
        """

        sessions = await self._call_repo_method("find_recent_sessions", limit)
        return self._finalize_export(sessions, format_config, priority_filter)

    async def export_file_errors(
        self, file_path: FilePath, *, format_config: ErrorExportFormat | None = None
    ) -> ClaudeIntegrationSession:
        """Export errors for a specific file path.

        Args:
            file_path: Path object identifying the file.
            format_config: Optional export format override.

        Returns:
            ClaudeIntegrationSession: Session describing the exported errors.
        """

        sessions = await self._call_repo_method("find_by_file_path", file_path)
        return self._finalize_export(sessions, format_config)

    async def export_recent_errors(
        self, *, format_config: ErrorExportFormat | None = None
    ) -> ClaudeIntegrationSession:
        """Export errors that occurred after the Claude adapter was last updated.

        Args:
            format_config: Optional export format override.

        Returns:
            ClaudeIntegrationSession: Session containing incrementally exported errors.
        """

        last_timestamp = self._get_adapter_last_updated()
        sessions = await self._call_repo_method("find_since_timestamp", last_timestamp)
        return self._finalize_export(sessions, format_config)

    # ------------------------------------------------------------------
    # 内部処理
    # ------------------------------------------------------------------
    async def _call_repo_method(self, name: str, *args: Any) -> list[FileQualityCheckSession]:
        """Call repository methods that may be synchronous or asynchronous.

        Args:
            name: Method name on the repository.
            *args: Arguments forwarded to the repository method.

        Returns:
            list[FileQualityCheckSession]: Normalised list of sessions.
        """

        method: Callable[..., Any] | None = getattr(self._session_repository, name, None)
        if method is None:
            self._log_warning(f"FileQualityCheckSessionRepository.{name} が未実装です")
            return []

        result = method(*args)
        if inspect.isawaitable(result):
            result = await result  # type: ignore[assignment]

        if result is None:
            return []

        if isinstance(result, Sequence):
            return list(result)

        return [result]

    def _finalize_export(
        self,
        sessions: Iterable[FileQualityCheckSession],
        format_config: ErrorExportFormat | None,
        priority_filter: str | None = None,
    ) -> ClaudeIntegrationSession:
        """Transform raw quality sessions into a Claude integration session.

        Args:
            sessions: Sessions retrieved from the repository.
            format_config: Optional export format override.
            priority_filter: Priority filter applied when present.

        Returns:
            ClaudeIntegrationSession: Populated integration session ready for Claude.
        """
        format_used = format_config or self._default_format
        if priority_filter:
            format_used = ErrorExportFormat(
                format_type=format_used.format_type,
                structure_version=format_used.structure_version,
                include_suggestions=format_used.include_suggestions,
                max_errors_per_file=format_used.max_errors_per_file,
                priority_filter=priority_filter,
            )

        quality_sessions = [self._normalize_session_errors(session) for session in sessions]
        integration_session = ClaudeIntegrationSession()
        if quality_sessions:
            integration_session.add_quality_sessions(quality_sessions)
        integration_session.suggestion_metadata.priority_focus = format_used.priority_filter

        # Claude Code向けデータ生成（旧API互換）
        export_payload = integration_session.export_for_claude(format_used)

        self._apply_format_helpers(quality_sessions, format_used)

        if self._claude_adapter is not None:
            # 旧実装互換: アダプターにセッションを渡して副作用を発生させる
            self._claude_adapter.export_errors_for_claude(quality_sessions)

        self._log_info("Claude向けエラー出力を完了", extra={"total_errors": integration_session.error_collection.total_errors})

        # 旧APIではClaudeAdapter側で書き出したファイル群を利用者が参照。
        # export_payloadは将来拡張用に返却情報として保持する。
        integration_session.metadata = {  # type: ignore[attr-defined]
            "export_result": export_payload,
            "export_completed_at": project_now().datetime,
        }

        return integration_session

    def _normalize_session_errors(self, session: FileQualityCheckSession) -> FileQualityCheckSession:
        """Normalize sessions so legacy test data remains compatible.

        Args:
            session: Session whose errors may require augmentation.

        Returns:
            FileQualityCheckSession: Session with synthetic errors attached when needed.
        """

        if getattr(session, "_normalized_for_claude", False):
            return session

        error_details = getattr(session, "_error_details", None) or []
        if error_details and not session.has_errors():
            for error in error_details:
                session.check_results.append(
                    CheckResult(
                        check_type=CheckType.SYNTAX,
                        status=CheckStatus.FAILED,
                        message=error.get("message", ""),
                        details=error,
                    )
                )
            session.status = CheckStatus.FAILED

        session._normalized_for_claude = True
        return session

    def _apply_format_helpers(self, sessions: list[FileQualityCheckSession], format_used: ErrorExportFormat) -> None:
        """Leverage the format service to reproduce legacy formatting behaviour.

        Args:
            sessions: Sessions prepared for export.
            format_used: Format configuration applied to the export.
        """

        if not self._format_service:
            return

        try:
            self._format_service.format_errors_for_claude(sessions)
            self._format_service.generate_fix_suggestions(sessions)
            self._format_service.create_priority_ranking(sessions)
            if format_used.priority_filter != "all":
                self._format_service.filter_by_priority(sessions, format_used.priority_filter)
        except AttributeError:
            # 古い実装が一部メソッドを欠いていても互換性維持のため握りつぶす
            pass

    def _get_adapter_last_updated(self) -> datetime | None:
        """Return the timestamp of the last export known by the adapter."""
        if self._claude_adapter is None:
            return None

        status = self._claude_adapter.get_claude_integration_status()
        last_updated = status.get("last_updated") if isinstance(status, dict) else None

        if isinstance(last_updated, datetime):
            return last_updated

        if isinstance(last_updated, str):
            try:
                return datetime.fromisoformat(last_updated)
            except ValueError:
                self._log_warning("last_updated を datetime に変換できませんでした", extra={"value": last_updated})
        return None

    def _log_info(self, message: str, extra: dict[str, Any] | None = None) -> None:
        """Log an informational message when a logger is available."""
        if hasattr(self._logger, "info"):
            self._logger.info(message, extra=extra)

    def _log_warning(self, message: str, extra: dict[str, Any] | None = None) -> None:
        """Log a warning message when a logger is available."""
        if hasattr(self._logger, "warning"):
            self._logger.warning(message, extra=extra)
