"""Feedback Loop for Continuous Improvement

Analyzes success metrics from workflow executions to improve
future agent generation. This creates a learning system that:

1. Tracks which agent configurations succeed
2. Identifies patterns in successful workflows
3. Adjusts agent recommendations based on historical data
4. Provides insights for manual tuning

Copyright 2026 Smart-AI-Memory
Licensed under Fair Source License 0.9
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

from ..config import _validate_file_path
from .blueprint import AgentBlueprint, WorkflowBlueprint
from .success import SuccessEvaluation

logger = logging.getLogger(__name__)


# =============================================================================
# FEEDBACK DATA STRUCTURES
# =============================================================================


@dataclass
class AgentPerformance:
    """Performance statistics for an agent template."""

    template_id: str
    total_uses: int = 0
    successful_uses: int = 0
    average_score: float = 0.0
    scores: list[float] = field(default_factory=list)

    # Context-specific performance
    by_domain: dict[str, dict[str, float]] = field(default_factory=dict)
    by_language: dict[str, dict[str, float]] = field(default_factory=dict)
    by_quality_focus: dict[str, dict[str, float]] = field(default_factory=dict)

    # Trend data
    recent_scores: list[tuple[str, float]] = field(default_factory=list)  # (timestamp, score)

    @property
    def success_rate(self) -> float:
        """Calculate success rate."""
        if self.total_uses == 0:
            return 0.0
        return self.successful_uses / self.total_uses

    @property
    def trend(self) -> str:
        """Determine performance trend."""
        if len(self.recent_scores) < 5:
            return "insufficient_data"

        recent_5 = [s for _, s in self.recent_scores[-5:]]
        older_5 = (
            [s for _, s in self.recent_scores[-10:-5]] if len(self.recent_scores) >= 10 else []
        )

        if not older_5:
            return "stable"

        recent_avg = sum(recent_5) / len(recent_5)
        older_avg = sum(older_5) / len(older_5)

        if recent_avg > older_avg * 1.1:
            return "improving"
        elif recent_avg < older_avg * 0.9:
            return "declining"
        else:
            return "stable"

    def record_use(
        self,
        success: bool,
        score: float,
        domain: str | None = None,
        languages: list[str] | None = None,
        quality_focus: list[str] | None = None,
    ) -> None:
        """Record a use of this agent."""
        self.total_uses += 1
        if success:
            self.successful_uses += 1

        self.scores.append(score)
        self.average_score = sum(self.scores) / len(self.scores)

        # Record with timestamp for trend analysis
        self.recent_scores.append((datetime.now().isoformat(), score))
        # Keep last 100 scores
        if len(self.recent_scores) > 100:
            self.recent_scores = self.recent_scores[-100:]

        # Record by context
        if domain:
            if domain not in self.by_domain:
                self.by_domain[domain] = {"uses": 0, "successes": 0, "total_score": 0}
            self.by_domain[domain]["uses"] += 1
            self.by_domain[domain]["successes"] += 1 if success else 0
            self.by_domain[domain]["total_score"] += score

        if languages:
            for lang in languages:
                if lang not in self.by_language:
                    self.by_language[lang] = {"uses": 0, "successes": 0, "total_score": 0}
                self.by_language[lang]["uses"] += 1
                self.by_language[lang]["successes"] += 1 if success else 0
                self.by_language[lang]["total_score"] += score

        if quality_focus:
            for qf in quality_focus:
                if qf not in self.by_quality_focus:
                    self.by_quality_focus[qf] = {"uses": 0, "successes": 0, "total_score": 0}
                self.by_quality_focus[qf]["uses"] += 1
                self.by_quality_focus[qf]["successes"] += 1 if success else 0
                self.by_quality_focus[qf]["total_score"] += score

    def get_score_for_context(
        self,
        domain: str | None = None,
        languages: list[str] | None = None,
        quality_focus: list[str] | None = None,
    ) -> float:
        """Get a weighted score for a specific context."""
        scores = []
        weights = []

        # Base score
        if self.total_uses > 0:
            scores.append(self.average_score)
            weights.append(1.0)

        # Domain-specific score
        if domain and domain in self.by_domain:
            d = self.by_domain[domain]
            if d["uses"] > 0:
                scores.append(d["total_score"] / d["uses"])
                weights.append(2.0)  # Higher weight for domain match

        # Language-specific score
        if languages:
            for lang in languages:
                if lang in self.by_language:
                    lang_stats = self.by_language[lang]
                    if lang_stats["uses"] > 0:
                        scores.append(lang_stats["total_score"] / lang_stats["uses"])
                        weights.append(1.5)

        # Quality focus score
        if quality_focus:
            for qf in quality_focus:
                if qf in self.by_quality_focus:
                    q = self.by_quality_focus[qf]
                    if q["uses"] > 0:
                        scores.append(q["total_score"] / q["uses"])
                        weights.append(1.5)

        if not scores:
            return 0.5  # Default neutral score

        # Weighted average
        return sum(s * w for s, w in zip(scores, weights, strict=False)) / sum(weights)

    def to_dict(self) -> dict[str, Any]:
        """Serialize to dictionary."""
        return {
            "template_id": self.template_id,
            "total_uses": self.total_uses,
            "successful_uses": self.successful_uses,
            "average_score": self.average_score,
            "success_rate": self.success_rate,
            "trend": self.trend,
            "by_domain": self.by_domain,
            "by_language": self.by_language,
            "by_quality_focus": self.by_quality_focus,
            "recent_scores": self.recent_scores[-20:],  # Last 20 for display
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> AgentPerformance:
        """Deserialize from dictionary."""
        perf = cls(template_id=data.get("template_id", ""))
        perf.total_uses = data.get("total_uses", 0)
        perf.successful_uses = data.get("successful_uses", 0)
        perf.average_score = data.get("average_score", 0.0)
        perf.by_domain = data.get("by_domain", {})
        perf.by_language = data.get("by_language", {})
        perf.by_quality_focus = data.get("by_quality_focus", {})
        perf.recent_scores = data.get("recent_scores", [])
        return perf


@dataclass
class WorkflowPattern:
    """Pattern of successful workflow configurations."""

    pattern_id: str
    domain: str
    agent_combination: list[str]  # List of template IDs
    stage_configuration: list[dict[str, Any]]
    uses: int = 0
    successes: int = 0
    average_score: float = 0.0

    @property
    def success_rate(self) -> float:
        """Calculate success rate."""
        if self.uses == 0:
            return 0.0
        return self.successes / self.uses

    def to_dict(self) -> dict[str, Any]:
        """Serialize to dictionary."""
        return {
            "pattern_id": self.pattern_id,
            "domain": self.domain,
            "agent_combination": self.agent_combination,
            "stage_configuration": self.stage_configuration,
            "uses": self.uses,
            "successes": self.successes,
            "average_score": self.average_score,
            "success_rate": self.success_rate,
        }


# =============================================================================
# FEEDBACK COLLECTOR
# =============================================================================


class FeedbackCollector:
    """Collects and stores feedback from workflow executions.

    Example:
        >>> collector = FeedbackCollector()
        >>> collector.record_execution(blueprint, evaluation)
        >>> performance = collector.get_agent_performance("security_reviewer")
    """

    def __init__(self, storage_path: str = ".attune/socratic/feedback"):
        """Initialize the collector.

        Args:
            storage_path: Path for feedback data storage
        """
        self.storage_path = Path(storage_path)
        self.storage_path.mkdir(parents=True, exist_ok=True)

        self._agent_performance: dict[str, AgentPerformance] = {}
        self._workflow_patterns: dict[str, WorkflowPattern] = {}

        self._load_data()

    def _load_data(self) -> None:
        """Load existing feedback data."""
        # Load agent performance
        perf_file = self.storage_path / "agent_performance.json"
        if perf_file.exists():
            try:
                with perf_file.open() as f:
                    data = json.load(f)
                for template_id, perf_data in data.items():
                    self._agent_performance[template_id] = AgentPerformance.from_dict(perf_data)
            except (json.JSONDecodeError, KeyError) as e:
                logger.warning(f"Failed to load agent performance: {e}")

        # Load workflow patterns
        patterns_file = self.storage_path / "workflow_patterns.json"
        if patterns_file.exists():
            try:
                with patterns_file.open() as f:
                    data = json.load(f)
                for pattern_id, pattern_data in data.items():
                    self._workflow_patterns[pattern_id] = WorkflowPattern(**pattern_data)
            except (json.JSONDecodeError, KeyError) as e:
                logger.warning(f"Failed to load workflow patterns: {e}")

    def _save_data(self) -> None:
        """Save feedback data to disk."""
        # Save agent performance
        perf_file = self.storage_path / "agent_performance.json"
        perf_data = {k: v.to_dict() for k, v in self._agent_performance.items()}
        validated_perf = _validate_file_path(str(perf_file))
        with validated_perf.open("w") as f:
            json.dump(perf_data, f, indent=2)

        # Save workflow patterns
        patterns_file = self.storage_path / "workflow_patterns.json"
        patterns_data = {k: v.to_dict() for k, v in self._workflow_patterns.items()}
        validated_patterns = _validate_file_path(str(patterns_file))
        with validated_patterns.open("w") as f:
            json.dump(patterns_data, f, indent=2)

    def record_execution(
        self,
        blueprint: WorkflowBlueprint,
        evaluation: SuccessEvaluation,
    ) -> None:
        """Record feedback from a workflow execution.

        Args:
            blueprint: The executed workflow blueprint
            evaluation: The success evaluation results
        """
        success = evaluation.overall_success
        score = evaluation.overall_score

        # Record for each agent
        for agent in blueprint.agents:
            template_id = agent.template_id or agent.spec.id

            if template_id not in self._agent_performance:
                self._agent_performance[template_id] = AgentPerformance(template_id=template_id)

            self._agent_performance[template_id].record_use(
                success=success,
                score=score,
                domain=blueprint.domain,
                languages=blueprint.supported_languages,
                quality_focus=blueprint.quality_focus,
            )

        # Record workflow pattern
        pattern_id = self._generate_pattern_id(blueprint)
        if pattern_id not in self._workflow_patterns:
            self._workflow_patterns[pattern_id] = WorkflowPattern(
                pattern_id=pattern_id,
                domain=blueprint.domain,
                agent_combination=[a.template_id or a.spec.id for a in blueprint.agents],
                stage_configuration=[s.to_dict() for s in blueprint.stages],
            )

        pattern = self._workflow_patterns[pattern_id]
        pattern.uses += 1
        if success:
            pattern.successes += 1
        # Rolling average
        pattern.average_score = (pattern.average_score * (pattern.uses - 1) + score) / pattern.uses

        self._save_data()
        logger.info(
            f"Recorded feedback for blueprint {blueprint.id[:8]}: success={success}, score={score:.2f}"
        )

    def _generate_pattern_id(self, blueprint: WorkflowBlueprint) -> str:
        """Generate a unique ID for a workflow pattern."""
        agents = sorted(a.template_id or a.spec.id for a in blueprint.agents)
        return f"{blueprint.domain}:{':'.join(agents)}"

    def get_agent_performance(self, template_id: str) -> AgentPerformance | None:
        """Get performance data for an agent template."""
        return self._agent_performance.get(template_id)

    def get_all_performance(self) -> dict[str, AgentPerformance]:
        """Get all agent performance data."""
        return self._agent_performance.copy()

    def get_best_agents_for_context(
        self,
        domain: str,
        languages: list[str] | None = None,
        quality_focus: list[str] | None = None,
        limit: int = 5,
    ) -> list[tuple[str, float]]:
        """Get the best performing agents for a context.

        Args:
            domain: Target domain
            languages: Target languages
            quality_focus: Quality attributes
            limit: Maximum number of results

        Returns:
            List of (template_id, score) tuples sorted by score
        """
        scored_agents = []

        for template_id, perf in self._agent_performance.items():
            score = perf.get_score_for_context(domain, languages, quality_focus)
            # Apply confidence penalty for low sample sizes
            confidence = min(perf.total_uses / 10, 1.0)  # Full confidence at 10+ uses
            adjusted_score = score * confidence + 0.5 * (1 - confidence)  # Blend with neutral
            scored_agents.append((template_id, adjusted_score))

        # Sort by score descending
        scored_agents.sort(key=lambda x: x[1], reverse=True)

        return scored_agents[:limit]

    def get_successful_patterns(
        self,
        domain: str | None = None,
        min_success_rate: float = 0.7,
        min_uses: int = 3,
    ) -> list[WorkflowPattern]:
        """Get successful workflow patterns.

        Args:
            domain: Filter by domain
            min_success_rate: Minimum success rate threshold
            min_uses: Minimum number of uses to be considered

        Returns:
            List of successful patterns
        """
        patterns = []

        for pattern in self._workflow_patterns.values():
            if domain and pattern.domain != domain:
                continue
            if pattern.uses < min_uses:
                continue
            if pattern.success_rate < min_success_rate:
                continue
            patterns.append(pattern)

        # Sort by success rate then by uses
        patterns.sort(key=lambda p: (p.success_rate, p.uses), reverse=True)

        return patterns

    def get_insights(self) -> dict[str, Any]:
        """Get aggregated insights from feedback data.

        Returns:
            Dictionary with various insights
        """
        insights: dict[str, Any] = {
            "total_agents_tracked": len(self._agent_performance),
            "total_patterns_tracked": len(self._workflow_patterns),
            "top_performing_agents": [],
            "declining_agents": [],
            "domain_insights": {},
            "recommendations": [],
        }

        # Top performing agents
        all_agents = [
            (tid, perf) for tid, perf in self._agent_performance.items() if perf.total_uses >= 5
        ]
        all_agents.sort(key=lambda x: x[1].average_score, reverse=True)
        insights["top_performing_agents"] = [
            {"template_id": tid, "score": perf.average_score, "uses": perf.total_uses}
            for tid, perf in all_agents[:5]
        ]

        # Declining agents
        for tid, perf in self._agent_performance.items():
            if perf.trend == "declining" and perf.total_uses >= 5:
                insights["declining_agents"].append(
                    {
                        "template_id": tid,
                        "current_score": perf.average_score,
                        "uses": perf.total_uses,
                    }
                )

        # Domain insights
        domains: dict[str, dict[str, Any]] = {}
        for perf in self._agent_performance.values():
            for domain, stats in perf.by_domain.items():
                if domain not in domains:
                    domains[domain] = {"total_uses": 0, "total_score": 0, "agents": set()}
                domains[domain]["total_uses"] += stats["uses"]
                domains[domain]["total_score"] += stats["total_score"]
                domains[domain]["agents"].add(perf.template_id)

        for domain, stats in domains.items():
            if stats["total_uses"] > 0:
                insights["domain_insights"][domain] = {
                    "average_score": stats["total_score"] / stats["total_uses"],
                    "total_uses": stats["total_uses"],
                    "agents_used": len(stats["agents"]),
                }

        # Generate recommendations
        insights["recommendations"] = self._generate_recommendations()

        return insights

    def _generate_recommendations(self) -> list[str]:
        """Generate improvement recommendations based on feedback."""
        recommendations = []

        # Check for underperforming agents
        for tid, perf in self._agent_performance.items():
            if perf.total_uses >= 10 and perf.success_rate < 0.5:
                recommendations.append(
                    f"Consider reviewing '{tid}' configuration - success rate is {perf.success_rate:.0%}"
                )

        # Check for agents that work well together
        successful_patterns = self.get_successful_patterns(min_success_rate=0.8, min_uses=5)
        for pattern in successful_patterns[:3]:
            agents = ", ".join(pattern.agent_combination)
            recommendations.append(
                f"Successful pattern for '{pattern.domain}': [{agents}] - {pattern.success_rate:.0%} success rate"
            )

        # Check for domains needing more data
        for domain, stats in self.get_insights().get("domain_insights", {}).items():
            if stats["total_uses"] < 5:
                recommendations.append(
                    f"More data needed for '{domain}' domain - only {stats['total_uses']} executions recorded"
                )

        return recommendations


# =============================================================================
# ADAPTIVE AGENT GENERATOR
# =============================================================================


class AdaptiveAgentGenerator:
    """Agent generator that uses feedback to improve recommendations.

    Wraps the standard AgentGenerator and adjusts recommendations
    based on historical performance data.

    Example:
        >>> generator = AdaptiveAgentGenerator()
        >>> agents = generator.generate_agents_for_requirements(requirements)
        >>> # Returns agents weighted by historical success
    """

    def __init__(self, feedback_collector: FeedbackCollector | None = None):
        """Initialize the adaptive generator.

        Args:
            feedback_collector: Feedback collector instance
        """
        from .generator import AgentGenerator

        self.base_generator = AgentGenerator()
        self.feedback = feedback_collector or FeedbackCollector()

    def generate_agents_for_requirements(
        self,
        requirements: dict[str, Any],
        use_feedback: bool = True,
    ) -> list[AgentBlueprint]:
        """Generate agents using feedback-informed recommendations.

        Args:
            requirements: Requirements from Socratic session
            use_feedback: Whether to use feedback data

        Returns:
            List of AgentBlueprints optimized based on feedback
        """
        # Get base recommendations
        base_agents = self.base_generator.generate_agents_for_requirements(requirements)

        if not use_feedback:
            return base_agents

        # Get context
        domain = requirements.get("domain", "general")
        languages = requirements.get("languages", [])
        quality_focus = requirements.get("quality_focus", [])

        # Get best agents for this context
        best_agents = self.feedback.get_best_agents_for_context(
            domain=domain,
            languages=languages,
            quality_focus=quality_focus,
            limit=10,
        )

        if not best_agents:
            return base_agents

        # Reorder and potentially add agents based on feedback
        agent_scores = dict(best_agents)

        # Score base agents
        scored_base = []
        for agent in base_agents:
            tid = agent.template_id or agent.spec.id
            feedback_score = agent_scores.get(tid, 0.5)
            scored_base.append((agent, feedback_score))

        # Sort by feedback score
        scored_base.sort(key=lambda x: x[1], reverse=True)

        # Check if any high-performing agents are missing
        base_ids = {a.template_id or a.spec.id for a in base_agents}
        for tid, score in best_agents:
            if tid not in base_ids and score > 0.7:
                # Add this high-performing agent
                try:
                    new_agent = self.base_generator.generate_agent_from_template(
                        tid,
                        customizations={
                            "languages": languages,
                            "quality_focus": quality_focus,
                        },
                    )
                    scored_base.append((new_agent, score))
                    logger.info(f"Added high-performing agent '{tid}' based on feedback")
                except ValueError:
                    pass  # Template not found

        # Return sorted agents
        return [agent for agent, _ in scored_base]

    def get_recommendation_explanation(
        self,
        requirements: dict[str, Any],
    ) -> dict[str, Any]:
        """Get explanation for agent recommendations.

        Args:
            requirements: Requirements dict

        Returns:
            Explanation of why agents were recommended
        """
        domain = requirements.get("domain", "general")
        languages = requirements.get("languages", [])
        quality_focus = requirements.get("quality_focus", [])

        best_agents = self.feedback.get_best_agents_for_context(
            domain=domain,
            languages=languages,
            quality_focus=quality_focus,
        )

        successful_patterns = self.feedback.get_successful_patterns(
            domain=domain,
            min_success_rate=0.7,
        )

        return {
            "context": {
                "domain": domain,
                "languages": languages,
                "quality_focus": quality_focus,
            },
            "recommended_agents": [
                {
                    "template_id": tid,
                    "score": score,
                    "performance": (
                        self.feedback.get_agent_performance(tid).to_dict()
                        if self.feedback.get_agent_performance(tid)
                        else None
                    ),
                }
                for tid, score in best_agents
            ],
            "successful_patterns": [p.to_dict() for p in successful_patterns[:3]],
            "data_quality": {
                "total_executions": sum(
                    p.total_uses for p in self.feedback.get_all_performance().values()
                ),
                "agents_with_data": len(
                    [p for p in self.feedback.get_all_performance().values() if p.total_uses >= 5]
                ),
            },
        }


# =============================================================================
# FEEDBACK LOOP INTEGRATION
# =============================================================================


class FeedbackLoop:
    """High-level integration for the feedback loop.

    Provides a simple interface to:
    1. Record execution results
    2. Get improved recommendations
    3. View insights

    Example:
        >>> loop = FeedbackLoop()
        >>>
        >>> # After workflow execution
        >>> loop.record(blueprint, evaluation)
        >>>
        >>> # For next generation
        >>> agents = loop.get_recommended_agents(requirements)
        >>>
        >>> # View insights
        >>> insights = loop.get_insights()
    """

    def __init__(
        self,
        storage_path: str = ".attune/socratic/feedback",
    ):
        """Initialize the feedback loop.

        Args:
            storage_path: Path for feedback storage
        """
        self.collector = FeedbackCollector(storage_path)
        self.adaptive_generator = AdaptiveAgentGenerator(self.collector)

    def record(
        self,
        blueprint: WorkflowBlueprint,
        evaluation: SuccessEvaluation,
    ) -> None:
        """Record execution results for learning.

        Args:
            blueprint: The executed blueprint
            evaluation: The success evaluation
        """
        self.collector.record_execution(blueprint, evaluation)

    def get_recommended_agents(
        self,
        requirements: dict[str, Any],
    ) -> list[AgentBlueprint]:
        """Get recommended agents using feedback data.

        Args:
            requirements: Requirements from Socratic session

        Returns:
            List of recommended agents
        """
        return self.adaptive_generator.generate_agents_for_requirements(requirements)

    def get_insights(self) -> dict[str, Any]:
        """Get aggregated insights.

        Returns:
            Dictionary with insights and recommendations
        """
        return self.collector.get_insights()

    def get_agent_stats(self, template_id: str) -> dict[str, Any] | None:
        """Get performance stats for a specific agent.

        Args:
            template_id: Agent template ID

        Returns:
            Performance statistics or None
        """
        perf = self.collector.get_agent_performance(template_id)
        return perf.to_dict() if perf else None

    def explain_recommendations(
        self,
        requirements: dict[str, Any],
    ) -> dict[str, Any]:
        """Explain why certain agents are recommended.

        Args:
            requirements: Requirements dict

        Returns:
            Explanation dictionary
        """
        return self.adaptive_generator.get_recommendation_explanation(requirements)
