from flask import Blueprint, request, jsonify
from tinydb import Query

from api.utils import send_event_for_current_user, get_validated_user_id
from events.types.child_rewards_set import ChildRewardsSet
from db.db import reward_db, child_db, pending_confirmations_db
from db.child_overrides import delete_overrides_for_entity
from events.types.event import Event
from events.types.event_types import EventType
from events.types.reward_modified import RewardModified
from events.types.child_reward_request import ChildRewardRequest
from models.reward import Reward

reward_api = Blueprint('reward_api', __name__)

# Reward endpoints
@reward_api.route('/reward/add', methods=['PUT'])
def add_reward():
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
    data = request.get_json()
    name = data.get('name')
    description = data.get('description')
    cost = data.get('cost')
    image = data.get('image_id', '')
    if not name or description is None or cost is None:
        return jsonify({'error': 'Name, description, and cost are required'}), 400
    reward = Reward(name=name, description=description, cost=cost, image_id=image, user_id=user_id)
    reward_db.insert(reward.to_dict())
    send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value, RewardModified(reward.id, RewardModified.OPERATION_ADD)))
    return jsonify({'message': f'Reward {name} added.'}), 201



@reward_api.route('/reward/<id>', methods=['GET'])
def get_reward(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
    RewardQuery = Query()
    result = reward_db.search((RewardQuery.id == id) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None)))
    if not result:
        return jsonify({'error': 'Reward not found'}), 404
    return jsonify(result[0]), 200

@reward_api.route('/reward/list', methods=['GET'])
def list_rewards():
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
    ids_param = request.args.get('ids')
    RewardQuery = Query()
    rewards = reward_db.search((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None))
    if ids_param is not None:
        if ids_param.strip() == '':
            rewards = []
        else:
            ids = set(ids_param.split(','))
            rewards = [reward for reward in rewards if reward.get('id') in ids]

    # Filter out default rewards if user-specific version exists (case/whitespace-insensitive)
    user_rewards = {r['name'].strip().lower(): r for r in rewards if r.get('user_id') == user_id}
    filtered_rewards = []
    for r in rewards:
        if r.get('user_id') is None and r['name'].strip().lower() in user_rewards:
            continue  # Skip default if user version exists
        filtered_rewards.append(r)
    
    # Sort: user-created items first (by name), then default items (by name)
    user_created = sorted([r for r in filtered_rewards if r.get('user_id') == user_id], key=lambda x: x['name'].lower())
    default_items = sorted([r for r in filtered_rewards if r.get('user_id') is None], key=lambda x: x['name'].lower())
    sorted_rewards = user_created + default_items
    
    return jsonify({'rewards': sorted_rewards}), 200

@reward_api.route('/reward/<id>', methods=['DELETE'])
def delete_reward(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
    RewardQuery = Query()
    reward = reward_db.get(RewardQuery.id == id)
    if not reward:
        return jsonify({'error': 'Reward not found'}), 404
    if reward.get('user_id') is None:
        import logging
        logging.warning(f"Forbidden delete attempt on system reward: id={id}, by user_id={user_id}")
        return jsonify({'error': 'System rewards cannot be deleted.'}), 403
    removed = reward_db.remove((RewardQuery.id == id) & (RewardQuery.user_id == user_id))
    if removed:
        # Cascade delete overrides for this reward
        deleted_count = delete_overrides_for_entity(id)
        if deleted_count > 0:
            import logging
            logging.info(f"Cascade deleted {deleted_count} overrides for reward {id}")
        
        # remove the reward id from any child's reward list
        ChildQuery = Query()
        for child in child_db.all():
            rewards = child.get('rewards', [])
            if id in rewards:
                rewards.remove(id)
                child_db.update({'rewards': rewards}, ChildQuery.id == child.get('id'))
                send_event_for_current_user(Event(EventType.CHILD_REWARDS_SET.value, ChildRewardsSet(id, rewards)))
        send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value, RewardModified(id, RewardModified.OPERATION_DELETE)))
        return jsonify({'message': f'Reward {id} deleted.'}), 200
    return jsonify({'error': 'Reward not found'}), 404

@reward_api.route('/reward/<id>/edit', methods=['PUT'])
def edit_reward(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
    RewardQuery = Query()
    existing = reward_db.get((RewardQuery.id == id) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None)))
    if not existing:
        return jsonify({'error': 'Reward not found'}), 404

    reward = Reward.from_dict(existing)
    is_dirty = False

    data = request.get_json(force=True) or {}
    if 'name' in data:
        name = (data.get('name') or '').strip()
        if not name:
            return jsonify({'error': 'Name cannot be empty'}), 400
        reward.name = name
        is_dirty = True

    if 'description' in data:
        # allow empty description (same behavior as add_reward)
        # note: front-end often submits an empty string, so don't block edits
        reward.description = data.get('description') or ''
        is_dirty = True

    if 'cost' in data:
        cost = data.get('cost')
        # allow numeric strings as well
        if isinstance(cost, str):
            try:
                cost = int(cost)
            except ValueError:
                return jsonify({'error': 'Cost must be an integer'}), 400
        if not isinstance(cost, int):
            return jsonify({'error': 'Cost must be an integer'}), 400
        if cost <= 0:
            return jsonify({'error': 'Cost must be a positive integer'}), 400
        reward.cost = cost
        is_dirty = True

    if 'image_id' in data:
        reward.image_id = data.get('image_id', '')
        is_dirty = True

    if not is_dirty:
        return jsonify({'error': 'No valid fields to update'}), 400

    # Reset pending reward requests when cost changes
    if 'cost' in data:
        PendingQuery = Query()
        pending_rewards = pending_confirmations_db.search(
            (PendingQuery.entity_id == id) & (PendingQuery.entity_type == 'reward') &
            (PendingQuery.status == 'pending')
        )
        for pr in pending_rewards:
            pending_confirmations_db.remove(
                (PendingQuery.child_id == pr['child_id']) & (PendingQuery.entity_id == id) &
                (PendingQuery.entity_type == 'reward') & (PendingQuery.status == 'pending')
            )
            send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value,
                ChildRewardRequest(pr['child_id'], id, ChildRewardRequest.REQUEST_CANCELLED)))

    if reward.user_id is None:  # public reward
        new_reward = Reward(name=reward.name, description=reward.description, cost=reward.cost, image_id=reward.image_id, user_id=user_id)
        reward_db.insert(new_reward.to_dict())
        send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value,
                                        RewardModified(new_reward.id, RewardModified.OPERATION_ADD)))
        return jsonify(new_reward.to_dict()), 200

    reward_db.update(reward.to_dict(), (RewardQuery.id == id) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None)))
    send_event_for_current_user(Event(EventType.REWARD_MODIFIED.value,
                                        RewardModified(id, RewardModified.OPERATION_EDIT)))
    return jsonify(reward.to_dict()), 200
