"""
Combat Repository - Database operations for combat encounters.

This service handles all CRUD operations for combat data stored in
dedicated database tables (combat_encounters, combat_rounds).

Separates combat persistence from the CombatService which handles
business logic and game mechanics.
"""

import json
from typing import Dict, Any, List, Optional
from datetime import datetime, timezone, timedelta
from uuid import uuid4

from appwrite.query import Query

from app.models.combat import CombatEncounter, Combatant
from app.models.enums import CombatStatus
from app.services.database_service import get_database_service, DatabaseService
from app.utils.logging import get_logger

logger = get_logger(__file__)


# =============================================================================
# Exceptions
# =============================================================================

class CombatEncounterNotFound(Exception):
    """Raised when combat encounter is not found in database."""
    pass


class CombatRoundNotFound(Exception):
    """Raised when combat round is not found in database."""
    pass


# =============================================================================
# Combat Repository
# =============================================================================

class CombatRepository:
    """
    Repository for combat encounter database operations.

    Handles:
    - Creating and reading combat encounters
    - Updating combat state during actions
    - Saving per-round history for logging and replay
    - Time-based cleanup of old combat data

    Tables:
    - combat_encounters: Main encounter state and metadata
    - combat_rounds: Per-round action history
    """

    # Table IDs
    ENCOUNTERS_TABLE = "combat_encounters"
    ROUNDS_TABLE = "combat_rounds"

    # Default retention period for cleanup (days)
    DEFAULT_RETENTION_DAYS = 7

    def __init__(self, db: Optional[DatabaseService] = None):
        """
        Initialize the combat repository.

        Args:
            db: Optional DatabaseService instance (for testing/injection)
        """
        self.db = db or get_database_service()
        logger.info("CombatRepository initialized")

    # =========================================================================
    # Encounter CRUD Operations
    # =========================================================================

    def create_encounter(
        self,
        encounter: CombatEncounter,
        session_id: str,
        user_id: str
    ) -> str:
        """
        Create a new combat encounter record.

        Args:
            encounter: CombatEncounter instance to persist
            session_id: Game session ID this encounter belongs to
            user_id: Owner user ID for authorization

        Returns:
            encounter_id of created record
        """
        created_at = self._get_timestamp()

        data = {
            'sessionId': session_id,
            'userId': user_id,
            'status': encounter.status.value,
            'roundNumber': encounter.round_number,
            'currentTurnIndex': encounter.current_turn_index,
            'turnOrder': json.dumps(encounter.turn_order),
            'combatantsData': json.dumps([c.to_dict() for c in encounter.combatants]),
            'combatLog': json.dumps(encounter.combat_log),
            'created_at': created_at,
        }

        self.db.create_row(
            table_id=self.ENCOUNTERS_TABLE,
            data=data,
            row_id=encounter.encounter_id
        )

        logger.info("Combat encounter created",
                   encounter_id=encounter.encounter_id,
                   session_id=session_id,
                   combatant_count=len(encounter.combatants))

        return encounter.encounter_id

    def get_encounter(self, encounter_id: str) -> Optional[CombatEncounter]:
        """
        Get a combat encounter by ID.

        Args:
            encounter_id: Encounter ID to fetch

        Returns:
            CombatEncounter or None if not found
        """
        logger.info("Fetching encounter from database",
                    encounter_id=encounter_id)

        row = self.db.get_row(self.ENCOUNTERS_TABLE, encounter_id)
        if not row:
            logger.warning("Encounter not found", encounter_id=encounter_id)
            return None

        logger.info("Raw database row data",
            encounter_id=encounter_id,
            currentTurnIndex=row.data.get('currentTurnIndex'),
            roundNumber=row.data.get('roundNumber'))

        encounter = self._row_to_encounter(row.data, encounter_id)

        logger.info("Encounter object created",
            encounter_id=encounter_id,
            current_turn_index=encounter.current_turn_index,
            turn_order=encounter.turn_order)

        return encounter

    def get_encounter_by_session(
        self,
        session_id: str,
        active_only: bool = True
    ) -> Optional[CombatEncounter]:
        """
        Get combat encounter for a session.

        Args:
            session_id: Game session ID
            active_only: If True, only return active encounters

        Returns:
            CombatEncounter or None if not found
        """
        queries = [Query.equal('sessionId', session_id)]
        if active_only:
            queries.append(Query.equal('status', CombatStatus.ACTIVE.value))

        rows = self.db.list_rows(
            table_id=self.ENCOUNTERS_TABLE,
            queries=queries,
            limit=1
        )

        if not rows:
            return None

        row = rows[0]
        return self._row_to_encounter(row.data, row.id)

    def get_user_active_encounters(self, user_id: str) -> List[CombatEncounter]:
        """
        Get all active encounters for a user.

        Args:
            user_id: User ID to query

        Returns:
            List of active CombatEncounter instances
        """
        rows = self.db.list_rows(
            table_id=self.ENCOUNTERS_TABLE,
            queries=[
                Query.equal('userId', user_id),
                Query.equal('status', CombatStatus.ACTIVE.value)
            ],
            limit=25
        )

        return [self._row_to_encounter(row.data, row.id) for row in rows]

    def update_encounter(self, encounter: CombatEncounter) -> None:
        """
        Update an existing combat encounter.

        Call this after each action to persist the updated state.

        Args:
            encounter: CombatEncounter with updated state
        """
        data = {
            'status': encounter.status.value,
            'roundNumber': encounter.round_number,
            'currentTurnIndex': encounter.current_turn_index,
            'turnOrder': json.dumps(encounter.turn_order),
            'combatantsData': json.dumps([c.to_dict() for c in encounter.combatants]),
            'combatLog': json.dumps(encounter.combat_log),
        }

        logger.info("Saving encounter to database",
            encounter_id=encounter.encounter_id,
            current_turn_index=encounter.current_turn_index,
            combat_log_entries=len(encounter.combat_log))

        self.db.update_row(
            table_id=self.ENCOUNTERS_TABLE,
            row_id=encounter.encounter_id,
            data=data
        )

        logger.info("Encounter saved successfully",
            encounter_id=encounter.encounter_id)

    def end_encounter(
        self,
        encounter_id: str,
        status: CombatStatus
    ) -> None:
        """
        Mark an encounter as ended.

        Args:
            encounter_id: Encounter ID to end
            status: Final status (VICTORY, DEFEAT, FLED)
        """
        ended_at = self._get_timestamp()

        data = {
            'status': status.value,
            'ended_at': ended_at,
        }

        self.db.update_row(
            table_id=self.ENCOUNTERS_TABLE,
            row_id=encounter_id,
            data=data
        )

        logger.info("Combat encounter ended",
                   encounter_id=encounter_id,
                   status=status.value)

    def delete_encounter(self, encounter_id: str) -> bool:
        """
        Delete an encounter and all its rounds.

        Args:
            encounter_id: Encounter ID to delete

        Returns:
            True if deleted successfully
        """
        # Delete rounds first
        self._delete_rounds_for_encounter(encounter_id)

        # Delete encounter
        result = self.db.delete_row(self.ENCOUNTERS_TABLE, encounter_id)

        logger.info("Combat encounter deleted", encounter_id=encounter_id)
        return result

    # =========================================================================
    # Round Operations
    # =========================================================================

    def save_round(
        self,
        encounter_id: str,
        session_id: str,
        round_number: int,
        actions: List[Dict[str, Any]],
        states_start: List[Combatant],
        states_end: List[Combatant]
    ) -> str:
        """
        Save a completed round's data for history/replay.

        Call this at the end of each round (after all combatants have acted).

        Args:
            encounter_id: Parent encounter ID
            session_id: Game session ID (denormalized for queries)
            round_number: Round number (1-indexed)
            actions: List of all actions taken this round
            states_start: Combatant states at round start
            states_end: Combatant states at round end

        Returns:
            round_id of created record
        """
        round_id = f"rnd_{uuid4().hex[:12]}"
        created_at = self._get_timestamp()

        data = {
            'encounterId': encounter_id,
            'sessionId': session_id,
            'roundNumber': round_number,
            'actionsData': json.dumps(actions),
            'combatantStatesStart': json.dumps([c.to_dict() for c in states_start]),
            'combatantStatesEnd': json.dumps([c.to_dict() for c in states_end]),
            'created_at': created_at,
        }

        self.db.create_row(
            table_id=self.ROUNDS_TABLE,
            data=data,
            row_id=round_id
        )

        logger.debug("Combat round saved",
                    round_id=round_id,
                    encounter_id=encounter_id,
                    round_number=round_number,
                    action_count=len(actions))

        return round_id

    def get_encounter_rounds(
        self,
        encounter_id: str,
        limit: int = 100
    ) -> List[Dict[str, Any]]:
        """
        Get all rounds for an encounter, ordered by round number.

        Args:
            encounter_id: Encounter ID to fetch rounds for
            limit: Maximum number of rounds to return

        Returns:
            List of round data dictionaries
        """
        rows = self.db.list_rows(
            table_id=self.ROUNDS_TABLE,
            queries=[Query.equal('encounterId', encounter_id)],
            limit=limit
        )

        rounds = []
        for row in rows:
            rounds.append({
                'round_id': row.id,
                'round_number': row.data.get('roundNumber'),
                'actions': json.loads(row.data.get('actionsData', '[]')),
                'states_start': json.loads(row.data.get('combatantStatesStart', '[]')),
                'states_end': json.loads(row.data.get('combatantStatesEnd', '[]')),
                'created_at': row.data.get('created_at'),
            })

        # Sort by round number
        return sorted(rounds, key=lambda r: r['round_number'])

    def get_session_combat_history(
        self,
        session_id: str,
        limit: int = 50
    ) -> List[Dict[str, Any]]:
        """
        Get combat history for a session.

        Returns summary of all encounters for the session.

        Args:
            session_id: Game session ID
            limit: Maximum encounters to return

        Returns:
            List of encounter summaries
        """
        rows = self.db.list_rows(
            table_id=self.ENCOUNTERS_TABLE,
            queries=[Query.equal('sessionId', session_id)],
            limit=limit
        )

        history = []
        for row in rows:
            history.append({
                'encounter_id': row.id,
                'status': row.data.get('status'),
                'round_count': row.data.get('roundNumber', 1),
                'created_at': row.data.get('created_at'),
                'ended_at': row.data.get('ended_at'),
            })

        # Sort by created_at descending (newest first)
        return sorted(history, key=lambda h: h['created_at'] or '', reverse=True)

    # =========================================================================
    # Cleanup Operations
    # =========================================================================

    def delete_encounters_by_session(self, session_id: str) -> int:
        """
        Delete all encounters for a session.

        Call this when a session is deleted.

        Args:
            session_id: Session ID to clean up

        Returns:
            Number of encounters deleted
        """
        rows = self.db.list_rows(
            table_id=self.ENCOUNTERS_TABLE,
            queries=[Query.equal('sessionId', session_id)],
            limit=100
        )

        deleted = 0
        for row in rows:
            # Delete rounds first
            self._delete_rounds_for_encounter(row.id)
            # Delete encounter
            self.db.delete_row(self.ENCOUNTERS_TABLE, row.id)
            deleted += 1

        if deleted > 0:
            logger.info("Deleted encounters for session",
                       session_id=session_id,
                       deleted_count=deleted)

        return deleted

    def delete_old_encounters(
        self,
        older_than_days: int = DEFAULT_RETENTION_DAYS
    ) -> int:
        """
        Delete ended encounters older than specified days.

        This is the main cleanup method for time-based retention.
        Should be scheduled to run periodically (daily recommended).

        Args:
            older_than_days: Delete encounters ended more than this many days ago

        Returns:
            Number of encounters deleted
        """
        cutoff = datetime.now(timezone.utc) - timedelta(days=older_than_days)
        cutoff_str = cutoff.isoformat().replace("+00:00", "Z")

        # Find old ended encounters
        # Note: We only delete ended encounters, not active ones
        rows = self.db.list_rows(
            table_id=self.ENCOUNTERS_TABLE,
            queries=[
                Query.notEqual('status', CombatStatus.ACTIVE.value),
                Query.lessThan('created_at', cutoff_str)
            ],
            limit=100
        )

        deleted = 0
        for row in rows:
            self._delete_rounds_for_encounter(row.id)
            self.db.delete_row(self.ENCOUNTERS_TABLE, row.id)
            deleted += 1

        if deleted > 0:
            logger.info("Deleted old combat encounters",
                       deleted_count=deleted,
                       older_than_days=older_than_days)

        return deleted

    # =========================================================================
    # Helper Methods
    # =========================================================================

    def _delete_rounds_for_encounter(self, encounter_id: str) -> int:
        """
        Delete all rounds for an encounter.

        Args:
            encounter_id: Encounter ID

        Returns:
            Number of rounds deleted
        """
        rows = self.db.list_rows(
            table_id=self.ROUNDS_TABLE,
            queries=[Query.equal('encounterId', encounter_id)],
            limit=100
        )

        for row in rows:
            self.db.delete_row(self.ROUNDS_TABLE, row.id)

        return len(rows)

    def _row_to_encounter(
        self,
        data: Dict[str, Any],
        encounter_id: str
    ) -> CombatEncounter:
        """
        Convert database row data to CombatEncounter object.

        Args:
            data: Row data dictionary
            encounter_id: Encounter ID

        Returns:
            Deserialized CombatEncounter
        """
        # Parse JSON fields
        combatants_data = json.loads(data.get('combatantsData', '[]'))
        combatants = [Combatant.from_dict(c) for c in combatants_data]

        turn_order = json.loads(data.get('turnOrder', '[]'))
        combat_log = json.loads(data.get('combatLog', '[]'))

        # Parse status enum
        status_str = data.get('status', 'active')
        status = CombatStatus(status_str)

        return CombatEncounter(
            encounter_id=encounter_id,
            combatants=combatants,
            turn_order=turn_order,
            current_turn_index=data.get('currentTurnIndex', 0),
            round_number=data.get('roundNumber', 1),
            combat_log=combat_log,
            status=status,
        )

    def _get_timestamp(self) -> str:
        """Get current UTC timestamp in ISO format."""
        return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


# =============================================================================
# Global Instance
# =============================================================================

_repository_instance: Optional[CombatRepository] = None


def get_combat_repository() -> CombatRepository:
    """
    Get the global CombatRepository instance.

    Returns:
        Singleton CombatRepository instance
    """
    global _repository_instance
    if _repository_instance is None:
        _repository_instance = CombatRepository()
    return _repository_instance
