"""Shared business logic for chore confirmation and reward actions.

Called from both child_api.py (JWT-authenticated endpoints) and
digest_action_api.py (token-authenticated endpoints). All functions take
user_id explicitly rather than reading it from the Flask request context.
"""
import logging
from datetime import datetime, timezone

from tinydb import Query

from db.db import child_db, task_db, reward_db, pending_confirmations_db, routine_db
from db.child_overrides import get_override
from db.chore_schedules import get_schedule
from db.routine_schedules import get_schedule as get_routine_schedule
from db.tracking import insert_tracking_event
from events.sse import send_event_to_user
from events.types.child_chore_confirmation import ChildChoreConfirmation
from events.types.child_routine_confirmation import ChildRoutineConfirmation
from events.types.child_reward_request import ChildRewardRequest
from events.types.child_reward_triggered import ChildRewardTriggered
from events.types.child_task_triggered import ChildTaskTriggered
from events.types.tracking_event_created import TrackingEventCreated
from events.types.event import Event
from events.types.event_types import EventType
from models.child import Child
from models.reward import Reward
from models.routine import Routine
from models.task import Task
from models.tracking_event import TrackingEvent
from utils.tracking_logger import log_tracking_event

logger = logging.getLogger(__name__)


def approve_chore(user_id: str, child_id: str, task_id: str) -> dict | None:
    """Award points for a completed chore and mark the pending confirmation approved.

    Returns a result dict on success, or None if the confirmation was already resolved.
    Raises ValueError if the child or task cannot be found.
    """
    ChildQ = Query()
    child_result = child_db.get((ChildQ.id == child_id) & (ChildQ.user_id == user_id))
    if not child_result:
        raise ValueError(f'Child {child_id} not found for user {user_id}')
    child = Child.from_dict(child_result)

    if task_id not in child.tasks:
        logger.info(f'Task {task_id} no longer assigned to child {child_id}; skipping approve')
        return None

    PendingQ = Query()
    existing = pending_confirmations_db.get(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == task_id) &
        (PendingQ.entity_type == 'chore') & (PendingQ.status == 'pending') &
        (PendingQ.user_id == user_id)
    )
    if not existing:
        logger.info(f'No pending chore for child {child_id}, task {task_id} — already resolved')
        return None

    TaskQ = Query()
    task_result = task_db.get(
        (TaskQ.id == task_id) & ((TaskQ.user_id == user_id) | (TaskQ.user_id == None))
    )
    if not task_result:
        raise ValueError(f'Task {task_id} not found')
    task = Task.from_dict(task_result)

    override = get_override(child_id, task_id)
    points_value = override.custom_value if override else task.points
    points_before = child.points
    child.points += points_value
    child_db.update({'points': child.points}, ChildQ.id == child_id)

    schedule = get_schedule(child_id, task_id)
    if schedule:
        now_str = datetime.now(timezone.utc).isoformat()
        pending_confirmations_db.update(
            {'status': 'approved', 'approved_at': now_str},
            (PendingQ.child_id == child_id) & (PendingQ.entity_id == task_id) &
            (PendingQ.entity_type == 'chore') & (PendingQ.user_id == user_id)
        )
    else:
        pending_confirmations_db.remove(
            (PendingQ.child_id == child_id) & (PendingQ.entity_id == task_id) &
            (PendingQ.entity_type == 'chore') & (PendingQ.user_id == user_id)
        )

    tracking_metadata = {
        'task_name': task.name,
        'task_type': task.type,
        'default_points': task.points,
    }
    if override:
        tracking_metadata['custom_points'] = override.custom_value
        tracking_metadata['has_override'] = True

    tracking_event = TrackingEvent.create_event(
        user_id=user_id, child_id=child_id, entity_type='chore', entity_id=task_id,
        action='approved', points_before=points_before, points_after=child.points,
        metadata=tracking_metadata,
    )
    insert_tracking_event(tracking_event)
    log_tracking_event(tracking_event)

    send_event_to_user(user_id, Event(EventType.TRACKING_EVENT_CREATED.value,
                                     TrackingEventCreated(tracking_event.id, child_id, 'chore', 'approved')))
    send_event_to_user(user_id, Event(EventType.CHILD_CHORE_CONFIRMATION.value,
                                     ChildChoreConfirmation(child_id, task_id, ChildChoreConfirmation.OPERATION_APPROVED)))
    send_event_to_user(user_id, Event(EventType.CHILD_TASK_TRIGGERED.value,
                                     ChildTaskTriggered(task_id, child_id, child.points)))

    return {'task_name': task.name, 'child_name': child.name, 'child_id': child_id, 'points': child.points}


def reject_chore(user_id: str, child_id: str, task_id: str) -> None:
    """Reject a pending chore confirmation. No-op if already resolved."""
    PendingQ = Query()
    existing = pending_confirmations_db.get(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == task_id) &
        (PendingQ.entity_type == 'chore') & (PendingQ.status == 'pending') &
        (PendingQ.user_id == user_id)
    )
    if not existing:
        logger.info(f'No pending chore for child {child_id}, task {task_id} — already resolved')
        return

    pending_confirmations_db.remove(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == task_id) &
        (PendingQ.entity_type == 'chore') & (PendingQ.user_id == user_id)
    )

    ChildQ = Query()
    child_result = child_db.get((ChildQ.id == child_id) & (ChildQ.user_id == user_id))
    if child_result:
        child = Child.from_dict(child_result)
        TaskQ = Query()
        task_result = task_db.get(
            (TaskQ.id == task_id) & ((TaskQ.user_id == user_id) | (TaskQ.user_id == None))
        )
        task_name = task_result.get('name') if task_result else 'Unknown'

        tracking_event = TrackingEvent.create_event(
            user_id=user_id, child_id=child_id, entity_type='chore', entity_id=task_id,
            action='rejected', points_before=child.points, points_after=child.points,
            metadata={'task_name': task_name},
        )
        insert_tracking_event(tracking_event)
        log_tracking_event(tracking_event)

        send_event_to_user(user_id, Event(EventType.TRACKING_EVENT_CREATED.value,
                                         TrackingEventCreated(tracking_event.id, child_id, 'chore', 'rejected')))

    send_event_to_user(user_id, Event(EventType.CHILD_CHORE_CONFIRMATION.value,
                                     ChildChoreConfirmation(child_id, task_id, ChildChoreConfirmation.OPERATION_REJECTED)))


def approve_reward_request(user_id: str, child_id: str, reward_id: str) -> dict:
    """Approve a child's pending reward request: deduct points and fire SSE events.

    Returns a result dict on success.
    Raises ValueError if child/reward not found or the child has insufficient points.
    """
    ChildQ = Query()
    child_result = child_db.get((ChildQ.id == child_id) & (ChildQ.user_id == user_id))
    if not child_result:
        raise ValueError(f'Child {child_id} not found for user {user_id}')
    child = Child.from_dict(child_result)

    if reward_id not in child.rewards:
        logger.info(f'Reward {reward_id} no longer assigned to child {child_id}; skipping approve')
        return None

    RewardQ = Query()
    reward_result = reward_db.get(
        (RewardQ.id == reward_id) & ((RewardQ.user_id == user_id) | (RewardQ.user_id == None))
    )
    if not reward_result:
        raise ValueError(f'Reward {reward_id} not found')
    reward = Reward.from_dict(reward_result)

    override = get_override(child_id, reward_id)
    cost_value = override.custom_value if override else reward.cost

    if child.points < cost_value:
        raise ValueError(f'Child {child_id} has insufficient points for reward {reward_id}')

    PendingQ = Query()
    existing = pending_confirmations_db.get(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == reward_id) &
        (PendingQ.entity_type == 'reward') & (PendingQ.status == 'pending') &
        (PendingQ.user_id == user_id)
    )
    if not existing:
        logger.info(f'No pending reward for child {child_id}, reward {reward_id} — already resolved')
        return None

    pending_confirmations_db.remove(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == reward_id) &
        (PendingQ.entity_type == 'reward') & (PendingQ.user_id == user_id)
    )
    send_event_to_user(user_id, Event(EventType.CHILD_REWARD_REQUEST.value,
                                     ChildRewardRequest(child_id, reward_id, ChildRewardRequest.REQUEST_GRANTED)))

    points_before = child.points
    child.points -= cost_value
    child_db.update({'points': child.points}, ChildQ.id == child_id)

    tracking_metadata = {
        'reward_name': reward.name,
        'reward_cost': reward.cost,
        'default_cost': reward.cost,
    }
    if override:
        tracking_metadata['custom_cost'] = override.custom_value
        tracking_metadata['has_override'] = True

    tracking_event = TrackingEvent.create_event(
        user_id=user_id, child_id=child_id, entity_type='reward', entity_id=reward_id,
        action='redeemed', points_before=points_before, points_after=child.points,
        metadata=tracking_metadata,
    )
    insert_tracking_event(tracking_event)
    log_tracking_event(tracking_event)

    send_event_to_user(user_id, Event(EventType.TRACKING_EVENT_CREATED.value,
                                     TrackingEventCreated(tracking_event.id, child_id, 'reward', 'redeemed')))
    send_event_to_user(user_id, Event(EventType.CHILD_REWARD_TRIGGERED.value,
                                     ChildRewardTriggered(reward_id, child_id, child.points)))

    return {'reward_name': reward.name, 'child_name': child.name, 'child_id': child_id, 'points': child.points}


def deny_reward(user_id: str, child_id: str, reward_id: str) -> dict | None:
    """Deny a child's pending reward request. No-op if already resolved.

    Returns a result dict on success, or None if already resolved.
    """
    PendingQ = Query()
    existing = pending_confirmations_db.get(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == reward_id) &
        (PendingQ.entity_type == 'reward') & (PendingQ.status == 'pending') &
        (PendingQ.user_id == user_id)
    )
    if not existing:
        logger.info(f'Reward request for child {child_id}, reward {reward_id} already resolved')
        return None

    pending_confirmations_db.remove(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == reward_id) &
        (PendingQ.entity_type == 'reward') & (PendingQ.user_id == user_id)
    )

    ChildQ = Query()
    child_result = child_db.get((ChildQ.id == child_id) & (ChildQ.user_id == user_id))
    child_name = 'Unknown'
    if child_result:
        child = Child.from_dict(child_result)
        child_name = child.name

        tracking_event = TrackingEvent.create_event(
            user_id=user_id, child_id=child_id, entity_type='reward', entity_id=reward_id,
            action='denied', points_before=child.points, points_after=child.points,
            metadata={},
        )
        insert_tracking_event(tracking_event)
        log_tracking_event(tracking_event)

        send_event_to_user(user_id, Event(EventType.TRACKING_EVENT_CREATED.value,
                                         TrackingEventCreated(tracking_event.id, child_id, 'reward', 'denied')))

    send_event_to_user(user_id, Event(EventType.CHILD_REWARD_REQUEST.value,
                                     ChildRewardRequest(child_id, reward_id, ChildRewardRequest.REQUEST_CANCELLED)))

    return {'child_name': child_name}


def approve_routine(user_id: str, child_id: str, routine_id: str) -> dict | None:
    """Award points for a completed routine and mark the pending confirmation approved.

    Returns a result dict on success, or None if already resolved.
    Raises ValueError if the child or routine cannot be found.
    """
    ChildQ = Query()
    child_result = child_db.get((ChildQ.id == child_id) & (ChildQ.user_id == user_id))
    if not child_result:
        raise ValueError(f'Child {child_id} not found for user {user_id}')
    child = Child.from_dict(child_result)

    if routine_id not in child.routines:
        logger.info(f'Routine {routine_id} no longer assigned to child {child_id}; skipping approve')
        return None

    PendingQ = Query()
    existing = pending_confirmations_db.get(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == routine_id) &
        (PendingQ.entity_type == 'routine') & (PendingQ.status == 'pending') &
        (PendingQ.user_id == user_id)
    )
    if not existing:
        logger.info(f'No pending routine for child {child_id}, routine {routine_id} — already resolved')
        return None

    RoutineQ = Query()
    routine_result = routine_db.get(
        (RoutineQ.id == routine_id) & ((RoutineQ.user_id == user_id) | (RoutineQ.user_id == None))
    )
    if not routine_result:
        raise ValueError(f'Routine {routine_id} not found')
    routine = Routine.from_dict(routine_result)

    override = get_override(child_id, routine_id)
    points_value = override.custom_value if override and override.entity_type == 'routine' else routine.points
    points_before = child.points
    child.points += points_value
    child_db.update({'points': child.points}, ChildQ.id == child_id)

    schedule = get_routine_schedule(child_id, routine_id)
    now_str = datetime.now(timezone.utc).isoformat()
    if schedule:
        pending_confirmations_db.update(
            {'status': 'approved', 'approved_at': now_str},
            (PendingQ.child_id == child_id) & (PendingQ.entity_id == routine_id) &
            (PendingQ.entity_type == 'routine') & (PendingQ.user_id == user_id)
        )
    else:
        pending_confirmations_db.remove(
            (PendingQ.child_id == child_id) & (PendingQ.entity_id == routine_id) &
            (PendingQ.entity_type == 'routine') & (PendingQ.user_id == user_id)
        )

    send_event_to_user(user_id, Event(EventType.CHILD_ROUTINE_CONFIRMATION.value,
                                     ChildRoutineConfirmation(child_id, routine_id, ChildRoutineConfirmation.OPERATION_APPROVED)))

    return {'routine_name': routine.name, 'child_name': child.name, 'child_id': child_id, 'points': child.points}


def reject_routine(user_id: str, child_id: str, routine_id: str) -> None:
    """Reject a pending routine confirmation. No-op if already resolved."""
    PendingQ = Query()
    existing = pending_confirmations_db.get(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == routine_id) &
        (PendingQ.entity_type == 'routine') & (PendingQ.status == 'pending') &
        (PendingQ.user_id == user_id)
    )
    if not existing:
        logger.info(f'No pending routine for child {child_id}, routine {routine_id} — already resolved')
        return

    pending_confirmations_db.remove(
        (PendingQ.child_id == child_id) & (PendingQ.entity_id == routine_id) &
        (PendingQ.entity_type == 'routine') & (PendingQ.user_id == user_id)
    )

    send_event_to_user(user_id, Event(EventType.CHILD_ROUTINE_CONFIRMATION.value,
                                     ChildRoutineConfirmation(child_id, routine_id, ChildRoutineConfirmation.OPERATION_REJECTED)))
