"""
CreateOS Pydantic Schemas.

Type-safe models for createOS entities (Task, Sprint, Epic, UserStory),
validated against schema/definitions/createos.yaml at runtime.

This module provides:
- CreateOSConfig: Runtime access to YAML configuration
- TaskSchema: Task entity schema with status/priority validation
- SprintSchema: Sprint entity schema
- EpicSchema: Epic entity schema
- UserStorySchema: User story schema
- Computed property helpers (is_active, is_blocked, is_stale)

Usage:
    from lightwave.schema.pydantic.sst.createos import (
        CreateOSConfig,
        TaskSchema,
        SprintSchema,
    )

    # Check valid task statuses
    statuses = CreateOSConfig.get_task_statuses()

    # Validate a task
    task = TaskSchema(
        id="abc123",
        title="Implement feature",
        status="in_progress",
        priority="high",
    )

    # Check computed properties
    is_active = CreateOSConfig.is_task_active(task.status)
"""

from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal
from functools import lru_cache
from pathlib import Path
from typing import Any
from uuid import UUID

import yaml
from pydantic import Field, field_validator

from lightwave.schema.pydantic.core.base import LightwaveBaseSchema

# =============================================================================
# YAML CONFIGURATION LOADER
# =============================================================================


def _get_yaml_path() -> Path:
    """Get path to createos/config.yaml (field definitions from __index.yaml registry)."""
    from lightwave.schema.core.paths import get_sst_path

    return get_sst_path("createos_config")


@lru_cache(maxsize=1)
def _load_createos_yaml() -> dict[str, Any]:
    """Load and cache createos.yaml."""
    yaml_path = _get_yaml_path()
    if not yaml_path.exists():
        return {
            "task_statuses": {"options": []},
            "task_priorities": {"options": []},
            "task_types": {"options": []},
            "task_categories": {"options": []},
            "sprint_statuses": {"options": []},
            "epic_statuses": {"options": []},
            "epic_priorities": {"options": []},
            "user_story_statuses": {"options": []},
            "user_story_priorities": {"options": []},
            "discovery_statuses": {"options": []},
            "interview_rounds": {"options": []},
            "agent_statuses": {"options": []},
            "computed": {},
            "health_scoring": {},
            "stale_thresholds": {},
        }
    with open(yaml_path) as f:
        return yaml.safe_load(f)


class CreateOSConfig:
    """
    Runtime configuration loaded from createos.yaml.

    Provides type-safe access to YAML-defined values with caching.
    """

    @classmethod
    def _yaml(cls) -> dict[str, Any]:
        """Get cached YAML data."""
        return _load_createos_yaml()

    # -------------------------------------------------------------------------
    # TASK STATUS
    # -------------------------------------------------------------------------

    @classmethod
    def get_task_statuses(cls) -> list[str]:
        """Get all valid task status values."""
        options = cls._yaml().get("task_statuses", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_task_status(cls, status: str) -> bool:
        """Check if a task status is valid."""
        return status in cls.get_task_statuses()

    @classmethod
    def get_task_status_config(cls, status: str) -> dict[str, Any]:
        """Get configuration for a task status."""
        options = cls._yaml().get("task_statuses", {}).get("options", [])
        for opt in options:
            if opt["value"] == status:
                return opt
        return {}

    @classmethod
    def is_task_active(cls, status: str) -> bool:
        """Check if task status is considered active."""
        config = cls.get_task_status_config(status)
        return config.get("is_active", False)

    @classmethod
    def is_task_blocked(cls, status: str) -> bool:
        """Check if task status indicates blocked."""
        config = cls.get_task_status_config(status)
        return config.get("is_blocked", False)

    @classmethod
    def is_task_completed(cls, status: str) -> bool:
        """Check if task status indicates completed."""
        config = cls.get_task_status_config(status)
        return config.get("is_completed", False)

    @classmethod
    def get_active_task_statuses(cls) -> list[str]:
        """Get all active task statuses."""
        options = cls._yaml().get("task_statuses", {}).get("options", [])
        return [opt["value"] for opt in options if opt.get("is_active", False)]

    @classmethod
    def get_completed_task_statuses(cls) -> list[str]:
        """Get all completed task statuses."""
        options = cls._yaml().get("task_statuses", {}).get("options", [])
        return [opt["value"] for opt in options if opt.get("is_completed", False)]

    @classmethod
    def get_valid_task_transitions(cls, from_status: str) -> list[str]:
        """Get valid status transitions from a given status.

        Args:
            from_status: The current task status.

        Returns:
            List of valid status values that can be transitioned to.
            Empty list if status is terminal or not found.
        """
        config = cls.get_task_status_config(from_status)
        return config.get("can_transition_to", [])

    @classmethod
    def is_valid_task_transition(cls, from_status: str, to_status: str) -> bool:
        """Check if a status transition is valid.

        Args:
            from_status: The current task status.
            to_status: The target task status.

        Returns:
            True if the transition is valid, False otherwise.
        """
        valid_transitions = cls.get_valid_task_transitions(from_status)
        return to_status in valid_transitions

    @classmethod
    def get_transition_error_message(cls, from_status: str, to_status: str) -> str:
        """Get a helpful error message for an invalid transition.

        Args:
            from_status: The current task status.
            to_status: The target task status.

        Returns:
            Error message with valid alternatives.
        """
        valid = cls.get_valid_task_transitions(from_status)
        if not valid:
            return f"Cannot transition from '{from_status}' - it is a terminal status."
        return f"Cannot transition from '{from_status}' to '{to_status}'. " f"Valid transitions: {', '.join(valid)}"

    # -------------------------------------------------------------------------
    # TASK PRIORITY
    # -------------------------------------------------------------------------

    @classmethod
    def get_task_priorities(cls) -> list[str]:
        """Get all valid task priority values."""
        options = cls._yaml().get("task_priorities", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_task_priority(cls, priority: str) -> bool:
        """Check if a task priority is valid."""
        return priority in cls.get_task_priorities()

    @classmethod
    def get_default_task_priority(cls) -> str:
        """Get the default task priority."""
        options = cls._yaml().get("task_priorities", {}).get("options", [])
        for opt in options:
            if opt.get("default"):
                return opt["value"]
        return "medium"

    @classmethod
    def get_priority_numeric_value(cls, priority: str) -> int:
        """Get numeric value for priority (for sorting)."""
        options = cls._yaml().get("task_priorities", {}).get("options", [])
        for opt in options:
            if opt["value"] == priority:
                return opt.get("numeric_value", 99)
        return 99

    # -------------------------------------------------------------------------
    # TASK TYPE
    # -------------------------------------------------------------------------

    @classmethod
    def get_task_types(cls) -> list[str]:
        """Get all valid task type values."""
        options = cls._yaml().get("task_types", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_task_type(cls, task_type: str) -> bool:
        """Check if a task type is valid."""
        return task_type in cls.get_task_types()

    # -------------------------------------------------------------------------
    # TASK CATEGORY
    # -------------------------------------------------------------------------

    @classmethod
    def get_task_categories(cls) -> list[str]:
        """Get all valid task category values."""
        options = cls._yaml().get("task_categories", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_task_category(cls, category: str) -> bool:
        """Check if a task category is valid."""
        return category in cls.get_task_categories()

    # -------------------------------------------------------------------------
    # SPRINT STATUS
    # -------------------------------------------------------------------------

    @classmethod
    def get_sprint_statuses(cls) -> list[str]:
        """Get all valid sprint status values."""
        options = cls._yaml().get("sprint_statuses", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_sprint_status(cls, status: str) -> bool:
        """Check if a sprint status is valid."""
        return status in cls.get_sprint_statuses()

    @classmethod
    def is_sprint_active(cls, status: str) -> bool:
        """Check if sprint status is considered active."""
        options = cls._yaml().get("sprint_statuses", {}).get("options", [])
        for opt in options:
            if opt["value"] == status:
                return opt.get("is_active", False)
        return False

    @classmethod
    def get_active_sprint_statuses(cls) -> list[str]:
        """Get all active sprint statuses."""
        options = cls._yaml().get("sprint_statuses", {}).get("options", [])
        return [opt["value"] for opt in options if opt.get("is_active", False)]

    # -------------------------------------------------------------------------
    # EPIC STATUS & PRIORITY
    # -------------------------------------------------------------------------

    @classmethod
    def get_epic_statuses(cls) -> list[str]:
        """Get all valid epic status values."""
        options = cls._yaml().get("epic_statuses", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_epic_status(cls, status: str) -> bool:
        """Check if an epic status is valid."""
        return status in cls.get_epic_statuses()

    @classmethod
    def get_epic_priorities(cls) -> list[str]:
        """Get all valid epic priority values."""
        options = cls._yaml().get("epic_priorities", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_epic_priority(cls, priority: str) -> bool:
        """Check if an epic priority is valid."""
        return priority in cls.get_epic_priorities()

    # -------------------------------------------------------------------------
    # USER STORY OPTIONS
    # -------------------------------------------------------------------------

    @classmethod
    def get_user_story_statuses(cls) -> list[str]:
        """Get all valid user story status values."""
        options = cls._yaml().get("user_story_statuses", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_user_story_status(cls, status: str) -> bool:
        """Check if a user story status is valid."""
        return status in cls.get_user_story_statuses()

    @classmethod
    def get_user_story_priorities(cls) -> list[str]:
        """Get all valid user story priority values."""
        options = cls._yaml().get("user_story_priorities", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def is_valid_user_story_priority(cls, priority: str) -> bool:
        """Check if a user story priority is valid."""
        return priority in cls.get_user_story_priorities()

    @classmethod
    def get_discovery_statuses(cls) -> list[str]:
        """Get all valid discovery status values."""
        options = cls._yaml().get("discovery_statuses", {}).get("options", [])
        return [opt["value"] for opt in options]

    @classmethod
    def get_interview_rounds(cls) -> list[str]:
        """Get all valid interview round values."""
        options = cls._yaml().get("interview_rounds", {}).get("options", [])
        return [opt["value"] for opt in options]

    # -------------------------------------------------------------------------
    # STALE THRESHOLDS
    # -------------------------------------------------------------------------

    @classmethod
    def get_stale_threshold_days(cls, entity_type: str) -> int:
        """Get stale threshold days for entity type."""
        thresholds = cls._yaml().get("stale_thresholds", {}).get(entity_type, {})
        return thresholds.get("stale_days", 14)

    @classmethod
    def get_warning_threshold_days(cls, entity_type: str) -> int:
        """Get warning threshold days for entity type."""
        thresholds = cls._yaml().get("stale_thresholds", {}).get(entity_type, {})
        return thresholds.get("warning_days", 7)

    @classmethod
    def get_abandoned_threshold_days(cls, entity_type: str) -> int:
        """Get abandoned threshold days for entity type."""
        thresholds = cls._yaml().get("stale_thresholds", {}).get(entity_type, {})
        return thresholds.get("abandoned_days", 30)

    # -------------------------------------------------------------------------
    # HEALTH SCORING
    # -------------------------------------------------------------------------

    @classmethod
    def get_health_scoring_config(cls, entity_type: str) -> dict[str, Any]:
        """Get health scoring config for entity type."""
        return cls._yaml().get("health_scoring", {}).get(entity_type, {})

    # -------------------------------------------------------------------------
    # CACHE MANAGEMENT
    # -------------------------------------------------------------------------

    @classmethod
    def clear_cache(cls) -> None:
        """Clear the YAML cache (for testing)."""
        _load_createos_yaml.cache_clear()


# =============================================================================
# TASK SCHEMA
# =============================================================================


class TaskSchema(LightwaveBaseSchema):
    """
    Task entity schema with validation.

    Attributes:
        id: Task UUID
        title: Task title
        description: Task description
        acceptance_criteria: Acceptance criteria
        status: Task status (validated against YAML)
        priority: Task priority (validated against YAML)
        task_type: Type of task (feature, bug, etc.)
        task_category: Category (frontend, backend, etc.)
        due_date: Due date
        do_date: Scheduled date
        sprint_id: Associated sprint
        epic_id: Associated epic
        assignee_id: Assigned user
        created_at: Creation timestamp
        updated_at: Last update timestamp
    """

    id: str | UUID = Field(..., description="Task identifier")
    title: str = Field(..., min_length=1, max_length=500, description="Task title")
    description: str = Field("", description="Task description")
    acceptance_criteria: str = Field("", description="Acceptance criteria")
    status: str = Field(..., description="Task status")
    priority: str = Field(..., description="Task priority")
    task_type: str = Field("feature", description="Task type")
    task_category: str = Field("other", description="Task category")
    due_date: date | None = Field(None, description="Due date")
    do_date: date | None = Field(None, description="Scheduled date")
    sprint_id: str | UUID | None = Field(None, description="Sprint ID")
    epic_id: str | UUID | None = Field(None, description="Epic ID")
    assignee_id: str | UUID | None = Field(None, description="Assignee ID")
    created_at: datetime | None = Field(None, description="Created timestamp")
    updated_at: datetime | None = Field(None, description="Updated timestamp")
    note: str = Field("", description="Additional notes")
    branch_name: str = Field("", description="Git branch name")
    pr_url: str = Field("", description="Pull request URL")

    @field_validator("status")
    @classmethod
    def validate_status(cls, v: str) -> str:
        """Validate status against YAML."""
        if not CreateOSConfig.is_valid_task_status(v):
            valid = CreateOSConfig.get_task_statuses()
            raise ValueError(f"Invalid task status '{v}'. Valid: {valid}")
        return v

    @field_validator("priority")
    @classmethod
    def validate_priority(cls, v: str) -> str:
        """Validate priority against YAML."""
        if not CreateOSConfig.is_valid_task_priority(v):
            valid = CreateOSConfig.get_task_priorities()
            raise ValueError(f"Invalid task priority '{v}'. Valid: {valid}")
        return v

    @field_validator("task_type")
    @classmethod
    def validate_task_type(cls, v: str) -> str:
        """Validate task type against YAML."""
        if v and not CreateOSConfig.is_valid_task_type(v):
            valid = CreateOSConfig.get_task_types()
            raise ValueError(f"Invalid task type '{v}'. Valid: {valid}")
        return v

    @field_validator("task_category")
    @classmethod
    def validate_task_category(cls, v: str) -> str:
        """Validate task category against YAML."""
        if v and not CreateOSConfig.is_valid_task_category(v):
            valid = CreateOSConfig.get_task_categories()
            raise ValueError(f"Invalid task category '{v}'. Valid: {valid}")
        return v

    @property
    def is_active(self) -> bool:
        """Check if task is in an active status."""
        return CreateOSConfig.is_task_active(self.status)

    @property
    def is_blocked(self) -> bool:
        """Check if task is blocked."""
        return CreateOSConfig.is_task_blocked(self.status)

    @property
    def is_completed(self) -> bool:
        """Check if task is completed."""
        return CreateOSConfig.is_task_completed(self.status)

    def is_stale(self, threshold_days: int | None = None) -> bool:
        """Check if task is stale (no updates for threshold days)."""
        if not self.is_active:
            return False
        if self.updated_at is None:
            return False
        threshold = threshold_days or CreateOSConfig.get_stale_threshold_days("task")
        from datetime import timedelta, timezone

        cutoff = datetime.now(timezone.utc) - timedelta(days=threshold)
        updated = self.updated_at
        if updated.tzinfo is None:
            updated = updated.replace(tzinfo=timezone.utc)
        return updated < cutoff


# =============================================================================
# SPRINT SCHEMA
# =============================================================================


class SprintSchema(LightwaveBaseSchema):
    """
    Sprint entity schema with validation.

    Attributes:
        id: Sprint UUID
        name: Sprint name
        status: Sprint status (validated against YAML)
        objectives: Sprint objectives
        start_date: Sprint start date
        end_date: Sprint end date
        quality_score: Quality score (0-100)
        created_at: Creation timestamp
        updated_at: Last update timestamp
    """

    id: str | UUID = Field(..., description="Sprint identifier")
    name: str = Field(..., min_length=1, max_length=200, description="Sprint name")
    status: str = Field(..., description="Sprint status")
    objectives: str = Field("", description="Sprint objectives")
    start_date: date | None = Field(None, description="Start date")
    end_date: date | None = Field(None, description="End date")
    quality_score: Decimal | None = Field(None, ge=0, le=100, description="Quality score")
    epic_id: str | UUID | None = Field(None, description="Epic ID")
    domain_id: str | UUID | None = Field(None, description="Domain ID")
    created_at: datetime | None = Field(None, description="Created timestamp")
    updated_at: datetime | None = Field(None, description="Updated timestamp")

    @field_validator("status")
    @classmethod
    def validate_status(cls, v: str) -> str:
        """Validate status against YAML."""
        if not CreateOSConfig.is_valid_sprint_status(v):
            valid = CreateOSConfig.get_sprint_statuses()
            raise ValueError(f"Invalid sprint status '{v}'. Valid: {valid}")
        return v

    @property
    def is_active(self) -> bool:
        """Check if sprint is active."""
        return CreateOSConfig.is_sprint_active(self.status)

    @property
    def days_remaining(self) -> int | None:
        """Calculate days remaining in sprint."""
        if self.end_date is None:
            return None
        today = date.today()
        delta = self.end_date - today
        return max(0, delta.days)


# =============================================================================
# EPIC SCHEMA
# =============================================================================


class EpicSchema(LightwaveBaseSchema):
    """
    Epic entity schema with validation.

    Attributes:
        id: Epic UUID
        name: Epic name
        status: Epic status
        subtitle: Epic subtitle
        log_line: Epic log line/description
        priority: Epic priority
        start_date: Start date
        target_date: Target completion date
        github_repo: Associated GitHub repo
    """

    id: str | UUID = Field(..., description="Epic identifier")
    name: str = Field(..., min_length=1, max_length=200, description="Epic name")
    status: str = Field(..., description="Epic status")
    subtitle: str = Field("", description="Epic subtitle")
    log_line: str = Field("", description="Epic description")
    priority: str = Field(..., description="Epic priority")
    start_date: date | None = Field(None, description="Start date")
    target_date: date | None = Field(None, description="Target date")
    github_repo: str = Field("", description="GitHub repo")
    created_at: datetime | None = Field(None, description="Created timestamp")
    updated_at: datetime | None = Field(None, description="Updated timestamp")

    @field_validator("status")
    @classmethod
    def validate_status(cls, v: str) -> str:
        """Validate status against YAML."""
        if not CreateOSConfig.is_valid_epic_status(v):
            valid = CreateOSConfig.get_epic_statuses()
            raise ValueError(f"Invalid epic status '{v}'. Valid: {valid}")
        return v

    @field_validator("priority")
    @classmethod
    def validate_priority(cls, v: str) -> str:
        """Validate priority against YAML."""
        if not CreateOSConfig.is_valid_epic_priority(v):
            valid = CreateOSConfig.get_epic_priorities()
            raise ValueError(f"Invalid epic priority '{v}'. Valid: {valid}")
        return v


# =============================================================================
# USER STORY SCHEMA
# =============================================================================


class UserStorySchema(LightwaveBaseSchema):
    """
    User story entity schema with validation.

    Attributes:
        id: User story UUID
        name: User story name
        description: Story description
        acceptance_criteria: Acceptance criteria
        status: Story status
        user_type: Target user type
        priority: Story priority (MoSCoW)
        story_points: Estimated story points
        discovery_status: Discovery phase status
        current_interview_round: Current interview round
    """

    id: str | UUID = Field(..., description="User story identifier")
    name: str = Field(..., min_length=1, max_length=500, description="Story name")
    description: str = Field("", description="Story description")
    acceptance_criteria: str = Field("", description="Acceptance criteria")
    status: str = Field(..., description="Story status")
    user_type: str = Field("", description="Target user type")
    priority: str = Field(..., description="Story priority (MoSCoW)")
    story_points: int | None = Field(None, ge=0, le=100, description="Story points")
    discovery_status: str = Field("not_started", description="Discovery status")
    current_interview_round: str = Field("initial", description="Interview round")
    epic_id: str | UUID | None = Field(None, description="Epic ID")
    created_at: datetime | None = Field(None, description="Created timestamp")
    updated_at: datetime | None = Field(None, description="Updated timestamp")

    @field_validator("status")
    @classmethod
    def validate_status(cls, v: str) -> str:
        """Validate status against YAML."""
        if not CreateOSConfig.is_valid_user_story_status(v):
            valid = CreateOSConfig.get_user_story_statuses()
            raise ValueError(f"Invalid user story status '{v}'. Valid: {valid}")
        return v

    @field_validator("priority")
    @classmethod
    def validate_priority(cls, v: str) -> str:
        """Validate priority against YAML."""
        if not CreateOSConfig.is_valid_user_story_priority(v):
            valid = CreateOSConfig.get_user_story_priorities()
            raise ValueError(f"Invalid user story priority '{v}'. Valid: {valid}")
        return v


# =============================================================================
# QUERY HELPERS
# =============================================================================


def get_active_task_filter() -> dict[str, list[str]]:
    """Get Django ORM filter for active tasks."""
    return {"status__in": CreateOSConfig.get_active_task_statuses()}


def get_active_sprint_filter() -> dict[str, list[str]]:
    """Get Django ORM filter for active sprints."""
    return {"status__in": CreateOSConfig.get_active_sprint_statuses()}


def get_blocked_task_filter() -> dict[str, list[str]]:
    """Get Django ORM filter for blocked tasks."""
    return {"status": "blocked"}


def get_completed_task_filter() -> dict[str, list[str]]:
    """Get Django ORM filter for completed tasks."""
    return {"status__in": CreateOSConfig.get_completed_task_statuses()}


# =============================================================================
# MODULE EXPORTS
# =============================================================================

__all__ = [
    "CreateOSConfig",
    "TaskSchema",
    "SprintSchema",
    "EpicSchema",
    "UserStorySchema",
    "get_active_task_filter",
    "get_active_sprint_filter",
    "get_blocked_task_filter",
    "get_completed_task_filter",
]
