"""
Dispute resolution service.
"""
from datetime import datetime
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models import (
    Dispute, DisputeStatus, DisputeVote,
    Assignment, AssignmentStatus, Participant, Marathon, Challenge, Game,
)
from app.services.telegram_notifier import telegram_notifier


class DisputeService:
    """Service for dispute resolution logic"""

    async def resolve_dispute(self, db: AsyncSession, dispute_id: int) -> tuple[str, int, int]:
        """
        Resolve a dispute based on votes.

        Returns:
            Tuple of (result_status, votes_valid, votes_invalid)
        """
        from app.models import BonusAssignment, BonusAssignmentStatus

        # Get dispute with votes, assignment and bonus_assignment
        result = await db.execute(
            select(Dispute)
            .options(
                selectinload(Dispute.votes),
                selectinload(Dispute.assignment).selectinload(Assignment.participant),
                selectinload(Dispute.bonus_assignment).selectinload(BonusAssignment.main_assignment).selectinload(Assignment.participant),
            )
            .where(Dispute.id == dispute_id)
        )
        dispute = result.scalar_one_or_none()

        if not dispute:
            raise ValueError(f"Dispute {dispute_id} not found")

        if dispute.status != DisputeStatus.OPEN.value:
            raise ValueError(f"Dispute {dispute_id} is already resolved")

        # Count votes
        votes_valid = sum(1 for v in dispute.votes if v.vote is True)
        votes_invalid = sum(1 for v in dispute.votes if v.vote is False)

        # Determine result: tie goes to the accused (valid)
        if votes_invalid > votes_valid:
            # Proof is invalid
            result_status = DisputeStatus.RESOLVED_INVALID.value
            if dispute.bonus_assignment_id:
                await self._handle_invalid_bonus_proof(db, dispute)
            else:
                await self._handle_invalid_proof(db, dispute)
        else:
            # Proof is valid (or tie)
            result_status = DisputeStatus.RESOLVED_VALID.value

        # Update dispute
        dispute.status = result_status
        dispute.resolved_at = datetime.utcnow()

        await db.commit()

        # Send Telegram notification about dispute resolution
        is_invalid = result_status == DisputeStatus.RESOLVED_INVALID.value
        if dispute.bonus_assignment_id:
            await self._notify_bonus_dispute_resolved(db, dispute, is_invalid)
        else:
            await self._notify_dispute_resolved(db, dispute, is_invalid)

        return result_status, votes_valid, votes_invalid

    async def _notify_dispute_resolved(
        self,
        db: AsyncSession,
        dispute: Dispute,
        is_valid: bool
    ) -> None:
        """Send notification about dispute resolution to the assignment owner."""
        try:
            # Get assignment with challenge/game and marathon info
            result = await db.execute(
                select(Assignment)
                .options(
                    selectinload(Assignment.participant),
                    selectinload(Assignment.challenge).selectinload(Challenge.game),
                    selectinload(Assignment.game),  # For playthrough
                )
                .where(Assignment.id == dispute.assignment_id)
            )
            assignment = result.scalar_one_or_none()
            if not assignment:
                return

            participant = assignment.participant

            # Get title and marathon_id based on assignment type
            if assignment.is_playthrough:
                title = f"Прохождение: {assignment.game.title}"
                marathon_id = assignment.game.marathon_id
            else:
                challenge = assignment.challenge
                title = challenge.title if challenge else "Unknown"
                marathon_id = challenge.game.marathon_id if challenge and challenge.game else 0

            # Get marathon
            result = await db.execute(
                select(Marathon).where(Marathon.id == marathon_id)
            )
            marathon = result.scalar_one_or_none()

            if marathon and participant:
                await telegram_notifier.notify_dispute_resolved(
                    db,
                    user_id=participant.user_id,
                    marathon_title=marathon.title,
                    challenge_title=title,
                    is_valid=is_valid
                )
        except Exception as e:
            print(f"[DisputeService] Failed to send notification: {e}")

    async def _notify_bonus_dispute_resolved(
        self,
        db: AsyncSession,
        dispute: Dispute,
        is_invalid: bool
    ) -> None:
        """Send notification about bonus dispute resolution to the assignment owner."""
        try:
            bonus_assignment = dispute.bonus_assignment
            main_assignment = bonus_assignment.main_assignment
            participant = main_assignment.participant

            # Get marathon info
            result = await db.execute(
                select(Game).where(Game.id == main_assignment.game_id)
            )
            game = result.scalar_one_or_none()
            if not game:
                return

            result = await db.execute(
                select(Marathon).where(Marathon.id == game.marathon_id)
            )
            marathon = result.scalar_one_or_none()

            # Get challenge title
            result = await db.execute(
                select(Challenge).where(Challenge.id == bonus_assignment.challenge_id)
            )
            challenge = result.scalar_one_or_none()
            title = f"Бонус: {challenge.title}" if challenge else "Бонусный челлендж"

            if marathon and participant:
                await telegram_notifier.notify_dispute_resolved(
                    db,
                    user_id=participant.user_id,
                    marathon_title=marathon.title,
                    challenge_title=title,
                    is_valid=not is_invalid
                )
        except Exception as e:
            print(f"[DisputeService] Failed to send bonus dispute notification: {e}")

    async def _handle_invalid_bonus_proof(self, db: AsyncSession, dispute: Dispute) -> None:
        """
        Handle the case when bonus proof is determined to be invalid.

        - Reset bonus assignment to PENDING
        - If main playthrough was already completed, subtract bonus points from participant
        """
        from app.models import BonusAssignment, BonusAssignmentStatus, AssignmentStatus

        bonus_assignment = dispute.bonus_assignment
        main_assignment = bonus_assignment.main_assignment
        participant = main_assignment.participant

        # If main playthrough was already completed, we need to subtract the bonus points
        if main_assignment.status == AssignmentStatus.COMPLETED.value:
            points_to_subtract = bonus_assignment.points_earned
            participant.total_points = max(0, participant.total_points - points_to_subtract)
            # Also reduce the points_earned on the main assignment
            main_assignment.points_earned = max(0, main_assignment.points_earned - points_to_subtract)
            print(f"[DisputeService] Subtracted {points_to_subtract} points from participant {participant.id}")

        # Reset bonus assignment
        bonus_assignment.status = BonusAssignmentStatus.PENDING.value
        bonus_assignment.proof_path = None
        bonus_assignment.proof_url = None
        bonus_assignment.proof_comment = None
        bonus_assignment.points_earned = 0
        bonus_assignment.completed_at = None

        print(f"[DisputeService] Bonus assignment {bonus_assignment.id} reset to PENDING due to invalid dispute")

    async def _handle_invalid_proof(self, db: AsyncSession, dispute: Dispute) -> None:
        """
        Handle the case when proof is determined to be invalid.

        - Mark assignment as RETURNED
        - Subtract points from participant
        - Reset streak if it was affected
        - For playthrough: also reset bonus assignments
        """
        from app.models import BonusAssignment, BonusAssignmentStatus

        assignment = dispute.assignment
        participant = assignment.participant

        # Subtract points that were earned
        points_to_subtract = assignment.points_earned
        participant.total_points = max(0, participant.total_points - points_to_subtract)

        # Reset streak - the completion was invalid so streak should be broken
        participant.current_streak = 0

        # Reset assignment
        assignment.status = AssignmentStatus.RETURNED.value
        assignment.points_earned = 0
        # Keep proof data so it can be reviewed

        # For playthrough: reset all bonus assignments
        if assignment.is_playthrough:
            result = await db.execute(
                select(BonusAssignment).where(BonusAssignment.main_assignment_id == assignment.id)
            )
            bonus_assignments = result.scalars().all()
            for ba in bonus_assignments:
                ba.status = BonusAssignmentStatus.PENDING.value
                ba.proof_path = None
                ba.proof_url = None
                ba.proof_comment = None
                ba.points_earned = 0
                ba.completed_at = None
            print(f"[DisputeService] Reset {len(bonus_assignments)} bonus assignments for playthrough {assignment.id}")

        print(f"[DisputeService] Assignment {assignment.id} marked as RETURNED, "
              f"subtracted {points_to_subtract} points from participant {participant.id}")

    async def get_pending_disputes(self, db: AsyncSession, older_than_hours: int = 24) -> list[Dispute]:
        """Get all open disputes (both regular and bonus) older than specified hours"""
        from datetime import timedelta
        from app.models import BonusAssignment

        cutoff_time = datetime.utcnow() - timedelta(hours=older_than_hours)

        result = await db.execute(
            select(Dispute)
            .options(
                selectinload(Dispute.assignment),
                selectinload(Dispute.bonus_assignment),
            )
            .where(
                Dispute.status == DisputeStatus.OPEN.value,
                Dispute.created_at < cutoff_time,
            )
        )
        return list(result.scalars().all())


# Global service instance
dispute_service = DisputeService()
