"""cortex/circadian/consolidation/dream_pipeline.py — Dream orchestration."""
from __future__ import annotations

import json, logging, time
from datetime import datetime, timezone
from typing import Any, List

from cortex.circadian.consolidation.dream_steps import DreamStepsMixin
from cortex.circadian.sleep import DreamReport

logger = logging.getLogger(__name__)


class DreamPipelineMixin(DreamStepsMixin):
    """Dream orchestration."""

    async def _run_dream_step(self, name: str, coro, report: "DreamReport") -> Any:
        """Run a single dream step with error isolation and timing."""
        t0 = time.monotonic()
        try:
            result = await coro
            elapsed = int((time.monotonic() - t0) * 1000)
            logger.info("[M6b] dream step %s — completed in %dms", name, elapsed)
            report.step_results.append(
                {"name": name, "status": "success", "duration_ms": elapsed}
            )
            return result
        except Exception as exc:
            elapsed = int((time.monotonic() - t0) * 1000)
            logger.error(
                "[M6b] dream step %s — FAILED after %dms: %s", name, elapsed, exc
            )
            report.step_results.append(
                {
                    "name": name,
                    "status": "failed",
                    "duration_ms": elapsed,
                    "error": str(exc),
                }
            )
            return None

    # ------------------------------------------------------------------
    # Public API — dream()
    # ------------------------------------------------------------------

    async def dream(self, owner_id: str = "default") -> DreamReport:
        """
        Nightly deep consolidation — Electric Sheep (target: <10 minutes).

        Pipeline:
          1. ORPHAN RECOVERY
          2. DEEP CONSOLIDATION
          3. CORNERSTONE EVOLUTION
          4. RECONSOLIDATION
          5. MEMORY DECAY
          6. ENTITY MERGE SUGGESTIONS
          7. BRAIN GRAPH REFRESH
          8. APPLY PENDING FEEDBACK
          9. DREAM JOURNAL
        """
        logger.info("[M6b] dream() — owner=%s", owner_id)
        t_start = time.monotonic()

        report = DreamReport(owner_id=owner_id)
        journal_lines: List[str] = [
            f"[M6b] Dream cycle started at {datetime.now(timezone.utc).isoformat()} UTC"
        ]

        # Emit DREAM_STARTED event
        if self.event_emitter is not None:
            try:
                from cortex.events.emitter import CortexEvent, EventType
                await self.event_emitter.emit(CortexEvent(
                    event_type=EventType.DREAM_STARTED,
                    owner_id=owner_id,
                    payload={"owner_id": owner_id},
                ))
            except Exception as exc:
                logger.debug("[M6b] event emit failed (DREAM_STARTED): %s", exc)

        # Step 0: EXTRACTION QUEUE RETRY
        logger.debug("[M6b] dream step 0 — extraction queue retry")
        queue_retried = await self._run_dream_step(
            "extraction_queue_retry", self._dream_retry_extraction_queue(owner_id), report
        )
        journal_lines.append(f"Extraction queue: {queue_retried or 0} entries retried")

        # Step 1: ORPHAN RECOVERY
        logger.debug("[M6b] dream step 1/10 — orphan recovery")
        orphans_recovered = await self._run_dream_step(
            "orphan_recovery", self._dream_recover_orphans(owner_id), report
        )
        report.orphans_recovered = orphans_recovered or 0
        journal_lines.append(f"Orphan recovery: {report.orphans_recovered} sessions recovered")

        # Step 2: DEEP CONSOLIDATION
        logger.debug("[M6b] dream step 2/10 — deep consolidation")
        claims_consolidated = await self._run_dream_step(
            "deep_consolidation", self._dream_deep_consolidation(owner_id), report
        )
        report.claims_consolidated = claims_consolidated or 0
        journal_lines.append(f"Deep consolidation: {report.claims_consolidated} claims reviewed")

        # Step 3: CORNERSTONE EVOLUTION
        logger.debug("[M6b] dream step 3/10 — cornerstone evolution")
        proposals = await self._run_dream_step(
            "cornerstone_evolution", self._dream_cornerstone_evolution(owner_id), report
        )
        report.cornerstone_proposals = proposals or 0
        journal_lines.append(f"Cornerstone proposals: {report.cornerstone_proposals} queued for review")

        # Step 4: RECONSOLIDATION
        logger.debug("[M6b] dream step 4/10 — reconsolidation")
        contradictions = await self._run_dream_step(
            "reconsolidation", self._dream_reconsolidation(owner_id), report
        )
        report.contradictions_resolved = contradictions or 0
        journal_lines.append(f"Reconsolidation: {report.contradictions_resolved} contradictions resolved")

        # Step 5: MEMORY DECAY (returns tuple)
        logger.debug("[M6b] dream step 5/10 — memory decay")
        decay_result = await self._run_dream_step(
            "memory_decay", self._dream_memory_decay(owner_id), report
        )
        if decay_result is not None:
            decayed, archived = decay_result
        else:
            decayed, archived = 0, 0
        report.claims_decayed = decayed
        report.claims_archived = archived
        journal_lines.append(f"Memory decay: {decayed} decayed, {archived} archived")

        # Step 5b: SOFT-DELETE PURGE (90-day retention enforcement)
        logger.debug("[M6b] dream step 5b — soft-delete purge (90-day retention)")
        purged = await self._run_dream_step(
            "soft_delete_purge", self._dream_purge_soft_deleted(owner_id), report
        )
        report.claims_purged = purged or 0
        journal_lines.append(f"Soft-delete purge: {report.claims_purged} expired claims permanently removed")

        # Step 6: ENTITY MERGE SUGGESTIONS
        logger.debug("[M6b] dream step 6/10 — entity merge suggestions")
        merges = await self._run_dream_step(
            "entity_merges", self._dream_entity_merges(owner_id), report
        )
        report.entity_merge_suggestions = merges or 0
        journal_lines.append(f"Entity merges: {report.entity_merge_suggestions} suggestions stored")

        # Step 7: PEER REFRESH (returns tuple)
        logger.debug("[M6b] dream step 7/10 — peer refresh")
        peer_result = await self._run_dream_step(
            "peer_refresh", self._dream_peer_refresh(owner_id), report
        )
        if peer_result is not None:
            peers_refreshed, peer_changes = peer_result
        else:
            peers_refreshed, peer_changes = 0, []
        report.peers_refreshed = peers_refreshed
        report.peer_changes = peer_changes
        journal_lines.append(
            f"Peer refresh: {peers_refreshed} peers refreshed, "
            f"{len(peer_changes)} trait changes detected"
        )

        # Step 8: BRAIN GRAPH REFRESH
        logger.debug("[M6b] dream step 8/10 — brain graph refresh")
        refreshed = await self._run_dream_step(
            "brain_graph_refresh", self._dream_brain_graph_refresh(owner_id), report
        )
        report.brain_graphs_refreshed = refreshed or 0
        journal_lines.append(f"Brain graph refresh: {report.brain_graphs_refreshed} graphs checked")

        # Step 9: APPLY PENDING FEEDBACK
        logger.debug("[M6b] dream step 9/10 — apply pending feedback")
        applied = await self._run_dream_step(
            "apply_feedback", self._dream_apply_feedback(owner_id), report
        )
        report.feedback_applied = applied or 0
        journal_lines.append(f"Feedback applied: {report.feedback_applied} records processed")

        # Step 10: DREAM JOURNAL (M6c — Curiosity Seeds + Cross-Memory Connections)
        logger.debug("[M6b] dream step 10/10 — curiosity seeds + dream journal")
        curiosity_report = await self._run_dream_step(
            "curiosity_and_journal",
            self._dream_curiosity_and_journal(
                owner_id=owner_id,
                dream_stats={
                    "orphans_recovered": report.orphans_recovered,
                    "claims_consolidated": report.claims_consolidated,
                    "contradictions_resolved": report.contradictions_resolved,
                    "claims_decayed": report.claims_decayed,
                    "claims_archived": report.claims_archived,
                    "cornerstone_proposals": report.cornerstone_proposals,
                    "entity_merge_suggestions": report.entity_merge_suggestions,
                    "brain_graphs_refreshed": report.brain_graphs_refreshed,
                    "feedback_applied": report.feedback_applied,
                    "peers_refreshed": report.peers_refreshed,
                    "peer_changes": len(report.peer_changes),
                },
            ),
            report,
        )

        report.duration_ms = int((time.monotonic() - t_start) * 1000)
        report.completed_at = datetime.now(timezone.utc).isoformat()

        if curiosity_report is not None and curiosity_report.journal_md:
            report.journal = curiosity_report.journal_md
            journal_lines.append(
                f"Curiosity seeds planted: {len(curiosity_report.seeds)}"
            )
            journal_lines.append(
                f"Cross-memory edges created: {curiosity_report.edges_created}"
            )
        else:
            journal_lines.append(
                f"Dream cycle complete in {report.duration_ms}ms at {report.completed_at}"
            )
            report.journal = "\n".join(journal_lines)

        # Step 11 (optional): DEEP DREAM PHASE — Gen1 Soul Restoration (#772)
        # Only runs if circadian.deep_dream_enabled is True.
        _deep_dream_enabled = getattr(self.config, "deep_dream_enabled", False)
        if _deep_dream_enabled:
            logger.info("[M6b] deep dream phase enabled — running Gen1 soul exploration")
            try:
                from cortex.circadian.deep_dream import DeepDreamPhase
                deep_dreamer = DeepDreamPhase(
                    storage=self.storage,
                    llm=self.llm,
                    config=self.config,
                )
                # Pass M6c seeds if available
                m6c_seeds = []
                if curiosity_report is not None:
                    m6c_seeds = [
                        {"id": s.id, "question": s.question, "theme": s.theme}
                        for s in curiosity_report.seeds
                    ]
                deep_dream_result = await deep_dreamer.run(
                    owner_id=owner_id,
                    m6c_seeds=m6c_seeds,
                )
                report.deep_dream = deep_dream_result.to_dict()
                journal_lines.append(
                    f"Deep dream: \"{deep_dream_result.thread_title}\" "
                    f"(depth={deep_dream_result.depth_score}, "
                    f"discovery={deep_dream_result.discovery_score}, "
                    f"resonance={deep_dream_result.emotional_resonance_score})"
                )
                logger.info(
                    "[M6b] deep dream complete: %s (avg=%.1f)",
                    deep_dream_result.dream_id,
                    deep_dream_result.avg_quality(),
                )
            except Exception as exc:
                logger.error("[M6b] deep dream phase failed (non-fatal): %s", exc)
                report.deep_dream = {"error": str(exc)}
        else:
            logger.debug("[M6b] deep dream phase disabled (circadian.deep_dream_enabled=false)")

        # Emit DREAM_COMPLETED event
        if self.event_emitter is not None:
            try:
                from cortex.events.emitter import CortexEvent, EventType
                await self.event_emitter.emit(CortexEvent(
                    event_type=EventType.DREAM_COMPLETED,
                    owner_id=owner_id,
                    payload={
                        "duration_ms": report.duration_ms,
                        "claims_consolidated": report.claims_consolidated,
                        "contradictions_resolved": report.contradictions_resolved,
                        "claims_decayed": report.claims_decayed,
                    },
                ))
            except Exception as exc:
                logger.debug("[M6b] event emit failed (DREAM_COMPLETED): %s", exc)

        # Persist dream report to session_record (latest sleeping session)
        await self._persist_dream_report(owner_id, report)

        steps_succeeded = sum(1 for s in report.step_results if s["status"] == "success")
        steps_failed = sum(1 for s in report.step_results if s["status"] == "failed")

        logger.info(
            "[M6b] dream() complete — owner=%s orphans=%d consolidated=%d "
            "contradictions=%d decayed=%d archived=%d proposals=%d merges=%d "
            "feedback=%d duration_ms=%d steps_succeeded=%d steps_failed=%d",
            owner_id, report.orphans_recovered, report.claims_consolidated,
            report.contradictions_resolved, report.claims_decayed,
            report.claims_archived, report.cornerstone_proposals,
            report.entity_merge_suggestions, report.feedback_applied,
            report.duration_ms, steps_succeeded, steps_failed,
        )
        return report

    # ------------------------------------------------------------------
    # Dream pipeline steps (private)
    # ------------------------------------------------------------------


    async def _persist_dream_report(self, owner_id: str, report: DreamReport) -> None:
        """Persist dream report JSON to the most recent sleeping session."""
        try:
            # Find the most recent session for this owner to attach the dream report
            active = await self.storage.get_active_session(owner_id=owner_id)
            if active:
                dream_json = json.dumps(
                    {
                        "orphans_recovered": report.orphans_recovered,
                        "claims_consolidated": report.claims_consolidated,
                        "contradictions_resolved": report.contradictions_resolved,
                        "claims_decayed": report.claims_decayed,
                        "claims_archived": report.claims_archived,
                        "cornerstone_proposals": report.cornerstone_proposals,
                        "entity_merge_suggestions": report.entity_merge_suggestions,
                        "brain_graphs_refreshed": report.brain_graphs_refreshed,
                        "feedback_applied": report.feedback_applied,
                        "peers_refreshed": report.peers_refreshed,
                        "peer_changes": report.peer_changes,
                        "duration_ms": report.duration_ms,
                        "completed_at": report.completed_at,
                    },
                    ensure_ascii=False,
                )
                await self.storage.update_session(
                    active.session_id,
                    dream_report=dream_json,
                    status="dreaming",
                )
        except Exception as exc:
            logger.warning("[M6b] could not persist dream report: %s", exc)


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
