"""
Pydantic models for session context.

These models represent the complete state of a development session, including:
- Git context (branch, commits, status)
- Task context (task, epic, sprint, user stories)
- Plan context (implementation plan, progress, risks)
- Sprint context (current sprint, objectives, progress)
- Knowledge context (related docs, plans, references)
- Environment context (versions, services, configuration)

All models are designed to be JSON-serializable and can be easily converted
to markdown for Claude Code consumption.
"""

from datetime import datetime
from pathlib import Path
from typing import List, Literal, Optional

from pydantic import BaseModel, Field, HttpUrl

# =============================================================================
# Git Models
# =============================================================================


class Commit(BaseModel):
    """A git commit."""

    hash: str
    message: str
    author: str
    timestamp: datetime


class UpstreamStatus(BaseModel):
    """Status relative to upstream branch."""

    ahead: int = Field(..., description="Commits ahead of upstream")
    behind: int = Field(..., description="Commits behind upstream")
    tracking: str = Field(..., description="Upstream branch name")


class GitContext(BaseModel):
    """Git repository state."""

    repo: str = Field(..., description="Repository name")
    branch: str = Field(..., description="Current branch name")
    is_clean: bool = Field(..., description="No uncommitted changes")
    recent_commits: List[Commit] = Field(default_factory=list, description="Recent commits (last 10)")
    upstream_status: Optional[UpstreamStatus] = Field(None, description="Status vs upstream")
    task_id_from_branch: Optional[str] = Field(None, description="Task ID extracted from branch name")


# =============================================================================
# createOS Models (aligned with Django models)
# =============================================================================


class Task(BaseModel):
    """A createOS task."""

    id: str
    short_id: str
    title: str
    description: Optional[str] = None
    acceptance_criteria: Optional[str] = None
    status: str
    priority: Optional[str] = None
    task_type: str
    task_category: str
    epic_id: Optional[str] = None
    epic_name: Optional[str] = None
    sprint_id: Optional[str] = None
    sprint_name: Optional[str] = None
    user_story_id: Optional[str] = None
    user_story_name: Optional[str] = None
    parent_task_id: Optional[str] = None
    parent_task_title: Optional[str] = None
    notion_url: Optional[HttpUrl] = None
    branch_name: Optional[str] = None
    pr_url: Optional[str] = None
    ai_summary: Optional[str] = None
    note: Optional[str] = None
    created_at: datetime
    updated_at: datetime


class Epic(BaseModel):
    """A createOS epic/project."""

    id: str
    short_id: str
    name: str
    status: str
    subtitle: Optional[str] = None
    log_line: Optional[str] = None
    github_repo: Optional[str] = None
    priority: Optional[str] = None
    start_date: Optional[datetime] = None
    target_date: Optional[datetime] = None


class Sprint(BaseModel):
    """A createOS sprint."""

    id: str
    short_id: str
    name: str
    status: str
    objectives: Optional[str] = None
    start_date: Optional[datetime] = None
    end_date: Optional[datetime] = None
    epic_id: Optional[str] = None
    epic_name: Optional[str] = None
    quality_score: Optional[float] = None


class UserStory(BaseModel):
    """A createOS user story."""

    id: str
    short_id: str
    name: str
    description: str
    acceptance_criteria: str
    status: str
    user_type: Optional[str] = None
    priority: Optional[str] = None
    story_points: Optional[int] = None
    epic_id: Optional[str] = None
    epic_name: Optional[str] = None
    sprint_id: Optional[str] = None
    sprint_name: Optional[str] = None


class Document(BaseModel):
    """A createOS document."""

    id: str
    short_id: str
    name: str
    content: str
    document_type: str
    status: str
    version: str
    vertical: Optional[str] = None
    security_level: Optional[str] = None
    ai_summary: Optional[str] = None
    url: Optional[HttpUrl] = None


# =============================================================================
# Plan Models
# =============================================================================


class Risk(BaseModel):
    """An implementation plan risk."""

    risk: str
    mitigation: str
    likelihood: str
    impact: str
    is_active: bool = True


class CriticalFile(BaseModel):
    """A critical file in an implementation plan."""

    path: str
    changes: str
    risk: str


class Dependency(BaseModel):
    """A dependency in an implementation plan."""

    name: str
    version: Optional[str] = None
    purpose: str
    installed: bool = False


class ImplementationPlan(BaseModel):
    """A createOS implementation plan."""

    id: str
    short_id: str
    title: str
    user_story_id: str
    user_story_name: Optional[str] = None
    task_id: Optional[str] = None
    task_title: Optional[str] = None
    summary: Optional[str] = None
    technical_approach: str
    test_strategy: Optional[str] = None
    critical_files: List[CriticalFile] = Field(default_factory=list)
    risks: List[Risk] = Field(default_factory=list)
    dependencies: List[Dependency] = Field(default_factory=list)
    status: Literal["draft", "approved", "in_progress", "completed", "superseded"] = "draft"
    version: int = 1


class PlanStatus(BaseModel):
    """Status of an implementation plan."""

    status: Literal["draft", "approved", "in_progress", "completed", "superseded"]
    version: int
    current_step: Optional[int] = None
    total_steps: int
    completed_steps: List[int] = Field(default_factory=list)


# =============================================================================
# Context Aggregators
# =============================================================================


class TaskContext(BaseModel):
    """Complete task context including related entities."""

    task: Task
    epic: Optional[Epic] = None
    sprint: Optional[Sprint] = None
    user_stories: List[UserStory] = Field(default_factory=list)
    subtasks: List[Task] = Field(default_factory=list)
    documents: List[Document] = Field(default_factory=list)


class PlanContext(BaseModel):
    """Implementation plan context."""

    plan: ImplementationPlan
    current_step: Optional[int] = None
    completed_steps: List[int] = Field(default_factory=list)
    status: PlanStatus
    risks_active: List[Risk] = Field(default_factory=list)
    plan_file_path: Optional[Path] = None


class SprintContext(BaseModel):
    """Current sprint context."""

    sprint: Sprint
    progress: float = Field(..., ge=0.0, le=1.0, description="Progress as a fraction (0.0 to 1.0)")
    tasks: List[Task] = Field(default_factory=list)
    objectives: List[str] = Field(default_factory=list)


class KnowledgeContext(BaseModel):
    """Knowledge graph context (related documents, plans, references)."""

    related_docs: List[Document] = Field(default_factory=list)
    related_plans: List[ImplementationPlan] = Field(default_factory=list)
    references: List[str] = Field(default_factory=list, description="URLs or file paths to relevant resources")


class EnvironmentContext(BaseModel):
    """Development environment context."""

    workspace_root: Path
    python_version: str
    node_version: Optional[str] = None
    uv_lock_hash: Optional[str] = None
    active_services: List[str] = Field(default_factory=list)
    aws_profile: Optional[str] = None


# =============================================================================
# Top-Level Session Context
# =============================================================================


class SessionContext(BaseModel):
    """Complete session context for Claude Code.

    This is the top-level model that aggregates all context sources.
    It can be serialized to JSON or converted to markdown for Claude consumption.
    """

    git: GitContext
    task: Optional[TaskContext] = None
    sprint: Optional[SprintContext] = None
    plan: Optional[PlanContext] = None
    knowledge: KnowledgeContext
    environment: EnvironmentContext
    timestamp: datetime = Field(default_factory=datetime.now)

    def to_markdown(self) -> str:
        """Convert session context to markdown for Claude consumption.

        Returns:
            Markdown-formatted string suitable for injection into Claude Code sessions.
        """
        from lightwave.context.formatters.markdown import format_session_context

        return format_session_context(self)

    def validate_state(self) -> List[str]:
        """Validate current state and return warnings/errors.

        Returns:
            List of warning/error messages. Empty list means all valid.
        """
        warnings = []

        # Check if branch name matches task ID
        if self.git.task_id_from_branch and self.task:
            if self.git.task_id_from_branch != self.task.task.short_id:
                warnings.append(
                    f"Branch task ID ({self.git.task_id_from_branch}) "
                    f"doesn't match loaded task ({self.task.task.short_id})"
                )

        # Check if task has required fields
        if self.task:
            if not self.task.task.acceptance_criteria:
                warnings.append(f"Task {self.task.task.short_id} has no acceptance criteria")

        # Check if plan is approved before starting work
        if self.plan and self.task:
            if self.plan.plan.status == "draft" and self.task.task.status == "in_progress":
                warnings.append("Implementation plan is still in draft but task is in progress")

        # Check if tests exist for in-progress tasks
        if self.task and self.task.task.status == "in_progress":
            # This would require filesystem checking, leave for future enhancement
            pass

        # Check if dependencies are installed
        if self.plan:
            uninstalled = [dep for dep in self.plan.plan.dependencies if not dep.installed]
            if uninstalled:
                warnings.append(f"Missing dependencies: {', '.join(dep.name for dep in uninstalled)}")

        return warnings

    def to_json_dict(self) -> dict:
        """Convert to JSON-serializable dictionary."""
        return self.model_dump(mode="json")
