"""
Skill Service - 技能管理服务层
负责技能的创建、查询、更新、删除等操作
"""

import hashlib
from datetime import UTC, datetime, timedelta
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, or_
from app.agents.schemas.learning import SkillCandidate
from app.agents.skills.models import SkillLifecycleDecision
from app.models.skill import Skill
from app.models.user import User


class SkillService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def create(self, user_id: str, data: dict) -> Skill:
        """创建新技能"""
        skill = Skill(
            owner_id=user_id,
            name=data.get("name"),
            description=data.get("description"),
            instructions=data.get("instructions"),
            agent_type=data.get("agent_type"),
            tools=data.get("tools", []),
            required_context=data.get("required_context", []),
            output_format=data.get("output_format"),
            visibility=data.get("visibility", "private"),
            team_id=data.get("team_id"),
            is_active=data.get("is_active", True),
            status=data.get("status", "active"),
            scope=data.get("scope", []),
            effectiveness=data.get("effectiveness", 0.0),
            review_after=data.get("review_after"),
        )
        self.db.add(skill)
        await self.db.commit()
        await self.db.refresh(skill)
        return skill

    async def get_by_id(self, skill_id: str) -> Optional[Skill]:
        """根据ID获取技能"""
        result = await self.db.execute(
            select(Skill).where(Skill.id == skill_id)
        )
        return result.scalar_one_or_none()

    async def get_by_name_for_user(self, user_id: str, name: str) -> Optional[Skill]:
        access_scope = or_(
            Skill.owner_id == user_id,
            Skill.visibility == "market",
            Skill.team_id == user_id,
        )
        result = await self.db.execute(
            select(Skill).where(and_(Skill.name == name, access_scope))
        )
        return result.scalar_one_or_none()

    async def list_for_user(
        self,
        user_id: str,
        agent_type: Optional[str] = None,
        visibility: Optional[str] = None,
    ) -> list[Skill]:
        """
        列出用户可访问的技能：自己的 + 市场的 + 团队的
        """
        access_scope = or_(
            Skill.owner_id == user_id,
            Skill.visibility == "market",
            Skill.team_id == user_id,
        )

        filters = [access_scope, Skill.is_active == True, Skill.status != "retired"]

        if agent_type:
            filters.append(Skill.agent_type == agent_type)

        if visibility:
            filters.append(Skill.visibility == visibility)

        query = select(Skill).where(and_(*filters))

        result = await self.db.execute(query)
        return list(result.scalars().all())

    async def update(self, skill_id: str, user_id: str, data: dict) -> Optional[Skill]:
        """更新技能（仅所有者可更新）"""
        skill = await self.get_by_id(skill_id)
        if not skill:
            return None

        # 检查是否是所有者
        if skill.owner_id != user_id:
            return None

        # 更新字段
        update_fields = [
            "name", "description", "instructions", "agent_type",
            "tools", "required_context", "output_format", "visibility",
            "team_id", "is_active", "status", "scope", "effectiveness", "review_after"
        ]

        for field in update_fields:
            if field in data:
                setattr(skill, field, data[field])

        await self.db.commit()
        await self.db.refresh(skill)
        return skill

    async def delete(self, skill_id: str, user_id: str) -> bool:
        """删除技能（仅所有者可删除）"""
        skill = await self.get_by_id(skill_id)
        if not skill:
            return False

        # 检查是否是所有者
        if skill.owner_id != user_id:
            return False

        await self.db.delete(skill)
        await self.db.commit()
        return True

    async def get_by_agent_type(self, agent_type: str) -> list[Skill]:
        """
        获取指定 agent_type 的技能（用于 agent 运行时：市场 + 私有）
        """
        result = await self.db.execute(
            select(Skill).where(
                and_(
                    Skill.agent_type == agent_type,
                    Skill.is_active == True,
                    Skill.status == "active",
                    or_(
                        Skill.visibility == "market",
                        Skill.visibility == "private"
                    )
                )
            )
        )
        return list(result.scalars().all())

    async def list_runtime_candidates(
        self,
        user_id: str,
        *,
        agent_type: Optional[str] = None,
        include_shadow: bool = True,
        include_learned: bool = True,
    ) -> list[Skill]:
        allowed_statuses = ["active", "shadow"] if include_shadow else ["active"]
        access_scope = or_(
            Skill.owner_id == user_id,
            Skill.visibility == "market",
            Skill.team_id == user_id,
        )

        filters = [
            access_scope,
            Skill.is_active == True,
            Skill.status.in_(allowed_statuses),
        ]
        if not include_learned:
            filters.append(Skill.is_builtin == True)
        if agent_type:
            filters.append(Skill.agent_type == agent_type)

        result = await self.db.execute(select(Skill).where(and_(*filters)))
        return list(result.scalars().all())

    async def upsert_learned_candidate(
        self,
        *,
        user_id: str,
        candidate: SkillCandidate,
        primary_agent: str | None,
        evidence_refs: list[dict] | None = None,
    ) -> SkillLifecycleDecision:
        source_hash = self._build_candidate_source_hash(candidate)
        skill = await self.get_by_name_for_user(user_id, candidate.name)
        if skill is None:
            review_after = datetime.now(UTC) + timedelta(days=7)
            skill = Skill(
                owner_id=user_id,
                name=candidate.name,
                description=candidate.summary,
                instructions=candidate.summary,
                agent_type=primary_agent or "master",
                tools=[],
                required_context=[],
                output_format=None,
                visibility="private",
                is_active=True,
                status="candidate",
                scope=[primary_agent or "master", "learned", candidate.candidate_type],
                effectiveness=candidate.confidence,
                review_after=review_after,
                candidate_count=1,
                candidate_source_hashes=[source_hash],
            )
            self.db.add(skill)
            await self.db.commit()
            await self.db.refresh(skill)
            return SkillLifecycleDecision(
                skill_name=skill.name,
                action="created_candidate",
                previous_status=None,
                new_status="candidate",
                reason="First learned candidate created from retrospective evidence.",
                evidence_refs=evidence_refs or [],
                confidence=candidate.confidence,
                review_after=review_after,
            )

        previous_status = skill.status
        known_hashes = list(skill.candidate_source_hashes or [])
        is_duplicate_candidate = source_hash in known_hashes
        if not is_duplicate_candidate:
            skill.candidate_count = int(skill.candidate_count or 0) + 1
            known_hashes.append(source_hash)
        skill.candidate_source_hashes = known_hashes
        current_effectiveness = float(skill.effectiveness or 0.0)
        skill.effectiveness = round(max(current_effectiveness, float(candidate.confidence or 0.0)), 3)
        skill.review_after = datetime.now(UTC) + timedelta(days=7)
        if primary_agent and primary_agent not in (skill.scope or []):
            skill.scope = [*(skill.scope or []), primary_agent]

        action = "no_change"
        reason = "Candidate evidence refreshed."
        if is_duplicate_candidate:
            reason = "Duplicate candidate evidence ignored for promotion counting."
        if (
            not is_duplicate_candidate
            and skill.status == "candidate"
            and skill.candidate_count >= 2
            and skill.effectiveness >= 0.6
        ):
            skill.status = "shadow"
            action = "promoted_to_shadow"
            reason = "Repeated candidate evidence promoted the learned skill to shadow."

        await self.db.commit()
        await self.db.refresh(skill)
        return SkillLifecycleDecision(
            skill_name=skill.name,
            action=action,
            previous_status=previous_status,
            new_status=skill.status,
            reason=reason,
            evidence_refs=evidence_refs or [],
            confidence=skill.effectiveness,
            review_after=skill.review_after,
        )

    async def record_activation_feedback(
        self,
        *,
        user_id: str,
        skill_name: str,
        outcome_score: float,
        evidence_refs: list[dict] | None = None,
    ) -> SkillLifecycleDecision | None:
        skill = await self.get_by_name_for_user(user_id, skill_name)
        if skill is None or skill.status not in {"shadow", "active", "deprecated"}:
            return None

        previous_status = skill.status
        previous_activation_count = int(skill.activation_count or 0)
        skill.activation_count = previous_activation_count + 1
        skill.last_activated_at = datetime.now(UTC)

        previous_effectiveness = float(skill.effectiveness or 0.0)
        if previous_activation_count <= 0:
            skill.effectiveness = round(outcome_score, 3)
        else:
            skill.effectiveness = round(
                ((previous_effectiveness * previous_activation_count) + outcome_score)
                / skill.activation_count,
                3,
            )

        action = "feedback_recorded"
        reason = "Activation outcome recorded."
        if skill.status == "shadow" and skill.activation_count >= 2 and skill.effectiveness >= 0.7:
            skill.status = "active"
            action = "promoted_to_active"
            reason = "Shadow skill proved effective enough to become active."
        elif skill.status == "active" and skill.activation_count >= 3 and skill.effectiveness < 0.35:
            skill.status = "deprecated"
            action = "degraded_to_deprecated"
            reason = "Active skill underperformed repeatedly and was deprecated."
        elif skill.status == "deprecated" and skill.activation_count >= 4 and skill.effectiveness < 0.2:
            skill.status = "retired"
            action = "retired"
            reason = "Deprecated skill stayed ineffective and was retired."
        elif skill.status == "deprecated" and skill.effectiveness >= 0.65 and outcome_score >= 0.8:
            skill.status = "active"
            action = "reactivated"
            reason = "Deprecated skill recovered with strong positive feedback."

        skill.review_after = datetime.now(UTC) + timedelta(days=7)
        await self.db.commit()
        await self.db.refresh(skill)
        return SkillLifecycleDecision(
            skill_name=skill.name,
            action=action,
            previous_status=previous_status,
            new_status=skill.status,
            reason=reason,
            evidence_refs=evidence_refs or [],
            confidence=skill.effectiveness,
            review_after=skill.review_after,
        )

    async def run_decay_review(
        self,
        *,
        user_id: str,
        as_of: datetime | None = None,
    ) -> list[SkillLifecycleDecision]:
        review_time = as_of or datetime.now(UTC)
        result = await self.db.execute(
            select(Skill).where(
                and_(
                    Skill.owner_id == user_id,
                    Skill.is_active == True,
                    Skill.status.in_(["shadow", "active", "deprecated"]),
                    Skill.review_after.is_not(None),
                    Skill.review_after <= review_time,
                )
            )
        )
        skills = list(result.scalars().all())
        decisions: list[SkillLifecycleDecision] = []
        for skill in skills:
            previous_status = skill.status
            action = "no_change"
            reason = "Review completed without status change."

            if skill.status == "shadow" and float(skill.effectiveness or 0.0) < 0.45:
                skill.status = "deprecated"
                action = "degraded_to_deprecated"
                reason = "Shadow skill review found low effectiveness."
            elif skill.status == "deprecated" and float(skill.effectiveness or 0.0) < 0.2:
                skill.status = "retired"
                action = "retired"
                reason = "Deprecated skill remained weak through review."

            skill.review_after = review_time + timedelta(days=7)
            decisions.append(
                SkillLifecycleDecision(
                    skill_name=skill.name,
                    action=action,
                    previous_status=previous_status,
                    new_status=skill.status,
                    reason=reason,
                    confidence=skill.effectiveness,
                    review_after=skill.review_after,
                )
            )

        await self.db.commit()
        return decisions

    @staticmethod
    def _build_candidate_source_hash(candidate: SkillCandidate) -> str:
        raw = (
            f"{candidate.name}|{candidate.summary}|"
            f"{','.join(candidate.source_pattern_ids)}|"
            f"{len(candidate.evidence_refs)}"
        ).encode("utf-8")
        return hashlib.sha1(raw).hexdigest()
