from time import sleep

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

from api.child_rewards import ChildReward
from api.child_tasks import ChildTask
from api.pending_reward import PendingReward as PendingRewardResponse
from api.reward_status import RewardStatus
from api.utils import send_event_for_current_user
from db.db import child_db, task_db, reward_db, pending_reward_db
from events.types.child_modified import ChildModified
from events.types.child_reward_request import ChildRewardRequest
from events.types.child_reward_triggered import ChildRewardTriggered
from events.types.child_rewards_set import ChildRewardsSet
from events.types.child_task_triggered import ChildTaskTriggered
from events.types.child_tasks_set import ChildTasksSet
from events.types.event import Event
from events.types.event_types import EventType
from models.child import Child
from models.pending_reward import PendingReward
from models.reward import Reward
from models.task import Task
import logging

child_api = Blueprint('child_api', __name__)
logger = logging.getLogger(__name__)

@child_api.route('/child/<name>', methods=['GET'])
@child_api.route('/child/<id>', methods=['GET'])
def get_child(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404
    return jsonify(Child.from_dict(result[0]).to_dict()), 200

@child_api.route('/child/add', methods=['PUT'])
def add_child():
    data = request.get_json()
    name = data.get('name')
    age = data.get('age')
    image = data.get('image_id', None)
    if not name:
        return jsonify({'error': 'Name is required'}), 400
    if not image:
        image = 'boy01'

    child = Child(name=name, age=age, image_id=image)
    child_db.insert(child.to_dict())
    resp = send_event_for_current_user(
        Event(EventType.CHILD_MODIFIED.value, ChildModified(child.id, ChildModified.OPERATION_ADD)))
    if resp:
        return resp
    return jsonify({'message': f'Child {name} added.'}), 201

@child_api.route('/child/<id>/edit', methods=['PUT'])
def edit_child(id):
    data = request.get_json()
    name = data.get('name', None)
    age = data.get('age', None)
    points = data.get('points', None)
    image = data.get('image_id', None)
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404
    child = Child.from_dict(result[0])

    if name is not None:
        child.name = name
    if age is not None:
        child.age = age
    if points is not None:
        child.points = points
    if image is not None:
        child.image_id = image

    # Check if points changed and handle pending rewards
    if points is not None:
        PendingQuery = Query()
        pending_rewards = pending_reward_db.search(PendingQuery.child_id == id)

        RewardQuery = Query()
        for pr in pending_rewards:
            pending = PendingReward.from_dict(pr)
            reward_result = reward_db.get(RewardQuery.id == pending.reward_id)
            if reward_result:
                reward = Reward.from_dict(reward_result)
                # If child can no longer afford the reward, remove the pending request
                if child.points < reward.cost:
                    pending_reward_db.remove(
                        (PendingQuery.child_id == id) & (PendingQuery.reward_id == reward.id)
                    )
                    resp = send_event_for_current_user(
                        Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(id, reward.id, ChildRewardRequest.REQUEST_CANCELLED)))
                    if resp:
                        return resp
    child_db.update(child.to_dict(), ChildQuery.id == id)
    resp = send_event_for_current_user(Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_EDIT)))
    if resp:
        return resp
    return jsonify({'message': f'Child {id} updated.'}), 200

@child_api.route('/child/list', methods=['GET'])
def list_children():
    children = child_db.all()
    return jsonify({'children': children}), 200

# Child DELETE
@child_api.route('/child/<id>', methods=['DELETE'])
def delete_child(id):
    ChildQuery = Query()
    if child_db.remove(ChildQuery.id == id):
        resp = send_event_for_current_user(Event(EventType.CHILD_MODIFIED.value, ChildModified(id, ChildModified.OPERATION_DELETE)))
        if resp:
            return resp
        return jsonify({'message': f'Child {id} deleted.'}), 200
    return jsonify({'error': 'Child not found'}), 404

@child_api.route('/child/<id>/assign-task', methods=['POST'])
def assign_task_to_child(id):
    data = request.get_json()
    task_id = data.get('task_id')
    if not task_id:
        return jsonify({'error': 'task_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    if task_id not in child.get('tasks', []):
        child['tasks'].append(task_id)
        child_db.update({'tasks': child['tasks']}, ChildQuery.id == id)
    return jsonify({'message': f'Task {task_id} assigned to {child['name']}.'}), 200

# python
@child_api.route('/child/<id>/set-tasks', methods=['PUT'])
def set_child_tasks(id):
    data = request.get_json() or {}
    task_ids = data.get('task_ids')
    if 'type' not in data:
        return jsonify({'error': 'type is required (good or bad)'}), 400
    task_type = data.get('type', 'good')
    if task_type not in ['good', 'bad']:
        return jsonify({'error': 'type must be either good or bad'}), 400
    is_good = task_type == 'good'
    if not isinstance(task_ids, list):
        return jsonify({'error': 'task_ids must be a list'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404
    child = Child.from_dict(result[0])
    new_task_ids = set(task_ids)

    # Add all existing child tasks of the opposite type
    for task in task_db.all():
        if task['id'] in child.tasks and task['is_good'] != is_good:
            new_task_ids.add(task['id'])

    # Convert back to list if needed
    new_tasks = list(new_task_ids)
    # Replace tasks with validated IDs
    child_db.update({'tasks': new_tasks}, ChildQuery.id == id)
    resp = send_event_for_current_user(Event(EventType.CHILD_TASKS_SET.value, ChildTasksSet(id, new_tasks)))
    if resp:
        return resp
    return jsonify({
        'message': f'Tasks set for child {id}.',
        'task_ids': new_tasks,
        'count': len(new_tasks)
    }), 200



@child_api.route('/child/<id>/remove-task', methods=['POST'])
def remove_task_from_child(id):
    data = request.get_json()
    task_id = data.get('task_id')
    if not task_id:
        return jsonify({'error': 'task_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    if task_id in child.get('tasks', []):
        child['tasks'].remove(task_id)
        child_db.update({'tasks': child['tasks']}, ChildQuery.id == id)
        return jsonify({'message': f'Task {task_id} removed from {child["name"]}.'}), 200
    return jsonify({'error': 'Task not assigned to child'}), 400

@child_api.route('/child/<id>/list-tasks', methods=['GET'])
def list_child_tasks(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    task_ids = child.get('tasks', [])

    TaskQuery = Query()
    child_tasks = []
    for tid in task_ids:
        task = task_db.get(TaskQuery.id == tid)
        if not task:
            continue
        ct = ChildTask(task.get('name'), task.get('is_good'), task.get('points'), task.get('image_id'), task.get('id'))
        child_tasks.append(ct.to_dict())

    return jsonify({'tasks': child_tasks}), 200

@child_api.route('/child/<id>/list-assignable-tasks', methods=['GET'])
def list_assignable_tasks(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    assigned_ids = set(child.get('tasks', []))

    # Collect all task ids from the task database
    all_task_ids = [t.get('id') for t in task_db.all() if t and t.get('id')]

    # Filter out already assigned
    assignable_ids = [tid for tid in all_task_ids if tid not in assigned_ids]

    # Fetch full task details and wrap in ChildTask
    TaskQuery = Query()
    assignable_tasks = []
    for tid in assignable_ids:
        task = task_db.get(TaskQuery.id == tid)
        if not task:
            continue
        ct = ChildTask(task.get('name'), task.get('is_good'), task.get('points'), task.get('image_id'), task.get('id'))
        assignable_tasks.append(ct.to_dict())

    return jsonify({'tasks': assignable_tasks, 'count': len(assignable_tasks)}), 200


@child_api.route('/child/<id>/list-all-tasks', methods=['GET'])
def list_all_tasks(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404
    has_type = "type" in request.args
    if has_type and request.args.get('type') not in ['good', 'bad']:
        return jsonify({'error': 'type must be either good or bad'}), 400
    good = request.args.get('type', False) == 'good'

    child = result[0]
    assigned_ids = set(child.get('tasks', []))

    # Get all tasks from database
    all_tasks = task_db.all()

    tasks = []

    for task in all_tasks:
        if not task or not task.get('id'):
            continue

        ct = ChildTask(
            task.get('name'),
            task.get('is_good'),
            task.get('points'),
            task.get('image_id'),
            task.get('id')
        )
        task_dict = ct.to_dict()
        if has_type and task.get('is_good') != good:
            continue

        task_dict.update({'assigned': task.get('id') in assigned_ids})
        tasks.append(task_dict)
        tasks.sort(key=lambda t: (not t['assigned'], t['name'].lower()))

    return jsonify({ 'tasks': tasks, 'count': len(tasks), 'list_type': 'task' }), 200


@child_api.route('/child/<id>/trigger-task', methods=['POST'])
def trigger_child_task(id):
    data = request.get_json()
    task_id = data.get('task_id')
    if not task_id:
        return jsonify({'error': 'task_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child: Child = Child.from_dict(result[0])
    if task_id not in child.tasks:
        return jsonify({'error': f'Task not found assigned to child {child.name}'}), 404
    # look up  the task and get the details
    TaskQuery = Query()
    task_result = task_db.search(TaskQuery.id == task_id)
    if not task_result:
        return jsonify({'error': 'Task not found in task database'}), 404
    task: Task = Task.from_dict(task_result[0])
    # update the child's points based on task type
    if task.is_good:
        child.points += task.points
    else:
        child.points -= task.points
        child.points = max(child.points, 0)
    # update the child in the database
    child_db.update({'points': child.points}, ChildQuery.id == id)
    resp = send_event_for_current_user(Event(EventType.CHILD_TASK_TRIGGERED.value, ChildTaskTriggered(task.id, child.id,  child.points)))
    if resp:
        return resp
    return jsonify({'message': f'{task.name} points assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200

@child_api.route('/child/<id>/assign-reward', methods=['POST'])
def assign_reward_to_child(id):
    data = request.get_json()
    reward_id = data.get('reward_id')
    if not reward_id:
        return jsonify({'error': 'reward_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    if reward_id not in child.get('rewards', []):
        child['rewards'].append(reward_id)
        child_db.update({'rewards': child['rewards']}, ChildQuery.id == id)
    return jsonify({'message': f'Reward {reward_id} assigned to {child["name"]}.'}), 200

@child_api.route('/child/<id>/list-all-rewards', methods=['GET'])
def list_all_rewards(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    assigned_ids = set(child.get('rewards', []))

    # Get all rewards from database
    all_rewards = reward_db.all()
    rewards = []

    for reward in all_rewards:
        if not reward or not reward.get('id'):
            continue

        cr = ChildReward(
            reward.get('name'),
            reward.get('cost'),
            reward.get('image_id'),
            reward.get('id')
        )

        reward_dict = cr.to_dict()

        reward_dict.update({'assigned': reward.get('id') in assigned_ids})
        rewards.append(reward_dict)
        rewards.sort(key=lambda t: (not t['assigned'], t['name'].lower()))

    return jsonify({
        'rewards': rewards,
        'rewards_count': len(rewards),
        'list_type': 'reward'
    }), 200


@child_api.route('/child/<id>/set-rewards', methods=['PUT'])
def set_child_rewards(id):
    data = request.get_json() or {}
    reward_ids = data.get('reward_ids')
    if not isinstance(reward_ids, list):
        return jsonify({'error': 'reward_ids must be a list'}), 400

    # Deduplicate and drop falsy values
    new_reward_ids = [rid for rid in dict.fromkeys(reward_ids) if rid]

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    # Optional: validate reward IDs exist in the reward DB
    RewardQuery = Query()
    valid_reward_ids = []
    for rid in new_reward_ids:
        if reward_db.get(RewardQuery.id == rid):
            valid_reward_ids.append(rid)

    # Replace rewards with validated IDs
    child_db.update({'rewards': valid_reward_ids}, ChildQuery.id == id)
    resp = send_event_for_current_user(Event(EventType.CHILD_REWARDS_SET.value, ChildRewardsSet(id, valid_reward_ids)))
    if resp:
        return resp
    return jsonify({
        'message': f'Rewards set for child {id}.',
        'reward_ids': valid_reward_ids,
        'count': len(valid_reward_ids)
    }), 200


@child_api.route('/child/<id>/remove-reward', methods=['POST'])
def remove_reward_from_child(id):
    data = request.get_json()
    reward_id = data.get('reward_id')
    if not reward_id:
        return jsonify({'error': 'reward_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    if reward_id in child.get('rewards', []):
        child['rewards'].remove(reward_id)
        child_db.update({'rewards': child['rewards']}, ChildQuery.id == id)
        return jsonify({'message': f'Reward {reward_id} removed from {child["name"]}.'}), 200
    return jsonify({'error': 'Reward not assigned to child'}), 400

@child_api.route('/child/<id>/list-rewards', methods=['GET'])
def list_child_rewards(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    reward_ids = child.get('rewards', [])

    RewardQuery = Query()
    child_rewards = []
    for rid in reward_ids:
        reward = reward_db.get(RewardQuery.id == rid)
        if not reward:
            continue
        cr = ChildReward(reward.get('name'), reward.get('cost'), reward.get('image_id'), reward.get('id'))
        child_rewards.append(cr.to_dict())

    return jsonify({'rewards': child_rewards}), 200

@child_api.route('/child/<id>/list-assignable-rewards', methods=['GET'])
def list_assignable_rewards(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = result[0]
    assigned_ids = set(child.get('rewards', []))

    all_reward_ids = [r.get('id') for r in reward_db.all() if r and r.get('id')]
    assignable_ids = [rid for rid in all_reward_ids if rid not in assigned_ids]

    RewardQuery = Query()
    assignable_rewards = []
    for rid in assignable_ids:
        reward = reward_db.get(RewardQuery.id == rid)
        if not reward:
            continue
        cr = ChildReward(reward.get('name'), reward.get('cost'), reward.get('image_id'), reward.get('id'))
        assignable_rewards.append(cr.to_dict())

    return jsonify({'rewards': assignable_rewards, 'count': len(assignable_rewards)}), 200

@child_api.route('/child/<id>/trigger-reward', methods=['POST'])
def trigger_child_reward(id):
    data = request.get_json()
    reward_id = data.get('reward_id')
    if not reward_id:
        return jsonify({'error': 'reward_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child: Child = Child.from_dict(result[0])
    if reward_id not in child.rewards:
        return jsonify({'error': f'Reward not found assigned to child {child.name}'}), 404
    # look up  the task and get the details
    RewardQuery = Query()
    reward_result = reward_db.search(RewardQuery.id == reward_id)
    if not reward_result:
        return jsonify({'error': 'Reward not found in reward database'}), 404
    reward: Reward = Reward.from_dict(reward_result[0])

    # Check if child has enough points
    logger.info(f'Child {child.name} has {child.points} points, reward {reward.name} costs {reward.cost} points')
    if child.points < reward.cost:
        points_needed = reward.cost - child.points
        return jsonify({
            'error': 'Insufficient points',
            'points_needed': points_needed,
            'current_points': child.points,
            'reward_cost': reward.cost
        }), 400

    # Remove matching pending reward requests for this child and reward
    PendingQuery = Query()
    removed = pending_reward_db.remove(
        (PendingQuery.child_id == child.id) & (PendingQuery.reward_id == reward.id)
    )
    if removed:
        send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(reward.id, child.id, ChildRewardRequest.REQUEST_GRANTED)))

    # update the child's points based on reward cost
    child.points -= reward.cost
    # update the child in the database
    child_db.update({'points': child.points}, ChildQuery.id == id)
    send_event_for_current_user(Event(EventType.CHILD_REWARD_TRIGGERED.value, ChildRewardTriggered(reward.id, child.id, child.points)))
    return jsonify({'message': f'{reward.name} assigned to {child.name}.', 'points': child.points, 'id': child.id}), 200

@child_api.route('/child/<id>/affordable-rewards', methods=['GET'])
def list_affordable_rewards(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = Child.from_dict(result[0])
    points = child.points
    reward_ids = child.rewards
    RewardQuery = Query()
    affordable = [
        Reward.from_dict(reward).to_dict() for reward_id in reward_ids
        if (reward := reward_db.get(RewardQuery.id == reward_id)) and points >= Reward.from_dict(reward).cost
    ]
    return jsonify({'affordable_rewards': affordable}), 200

@child_api.route('/child/<id>/reward-status', methods=['GET'])
def reward_status(id):
    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = Child.from_dict(result[0])
    points = child.points
    reward_ids = child.rewards

    RewardQuery = Query()
    statuses = []
    for reward_id in reward_ids:
        reward: Reward = Reward.from_dict(reward_db.get(RewardQuery.id == reward_id))
        if not reward:
            continue
        points_needed = max(0, reward.cost - points)
        #check to see if this reward id and child id is in the pending rewards db if so set its redeeming flag to true
        pending_query = Query()
        pending = pending_reward_db.get((pending_query.child_id == child.id) & (pending_query.reward_id == reward.id))
        status = RewardStatus(reward.id, reward.name, points_needed, reward.cost, pending is not None, reward.image_id)
        statuses.append(status.to_dict())

    statuses.sort(key=lambda s: (not s['redeeming'], s['cost']))
    return jsonify({'reward_status': statuses}), 200


@child_api.route('/child/<id>/request-reward', methods=['POST'])
def request_reward(id):
    data = request.get_json()
    reward_id = data.get('reward_id')
    if not reward_id:
        return jsonify({'error': 'reward_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = Child.from_dict(result[0])
    if reward_id not in child.rewards:
        return jsonify({'error': f'Reward not assigned to child {child.name}'}), 404

    RewardQuery = Query()
    reward_result = reward_db.search(RewardQuery.id == reward_id)
    if not reward_result:
        return jsonify({'error': 'Reward not found in reward database'}), 404

    reward = Reward.from_dict(reward_result[0])

    # Check if child has enough points
    logger.info(f'Child {child.name} has {child.points} points, reward {reward.name} costs {reward.cost} points')
    if child.points < reward.cost:
        points_needed = reward.cost - child.points
        return jsonify({
            'error': 'Insufficient points',
            'points_needed': points_needed,
            'current_points': child.points,
            'reward_cost': reward.cost
        }), 400

    pending = PendingReward(child_id=child.id, reward_id=reward.id)
    pending_reward_db.insert(pending.to_dict())
    logger.info(f'Pending reward request created for child {child.name} for reward {reward.name}')
    send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(child.id, reward.id, ChildRewardRequest.REQUEST_CREATED)))
    return jsonify({
        'message': f'Reward request for {reward.name} submitted for {child.name}.',
        'reward_id': reward.id,
        'reward_name': reward.name,
        'child_id': child.id,
        'child_name': child.name,
        'cost': reward.cost
    }), 200

@child_api.route('/child/<id>/cancel-request-reward', methods=['POST'])
def cancel_request_reward(id):
    data = request.get_json()
    reward_id = data.get('reward_id')
    if not reward_id:
        return jsonify({'error': 'reward_id is required'}), 400

    ChildQuery = Query()
    result = child_db.search(ChildQuery.id == id)
    if not result:
        return jsonify({'error': 'Child not found'}), 404

    child = Child.from_dict(result[0])

    # Remove matching pending reward request
    PendingQuery = Query()
    removed = pending_reward_db.remove(
        (PendingQuery.child_id == child.id) & (PendingQuery.reward_id == reward_id)
    )

    if not removed:
        return jsonify({'error': 'No pending request found for this reward'}), 404

    # Notify user that the request was cancelled
    resp = send_event_for_current_user(Event(EventType.CHILD_REWARD_REQUEST.value, ChildRewardRequest(child.id, reward_id, ChildRewardRequest.REQUEST_CANCELLED)))
    if resp:
        return resp
    return jsonify({
        'message': f'Reward request cancelled for {child.name}.',
        'child_id': child.id,
        'reward_id': reward_id,
        'removed_count': len(removed)
    }), 200



@child_api.route('/pending-rewards', methods=['GET'])
def list_pending_rewards():
    pending_rewards = pending_reward_db.all()
    reward_responses = []

    RewardQuery = Query()
    ChildQuery = Query()

    for pr in pending_rewards:
        pending = PendingReward.from_dict(pr)

        # Look up reward details
        reward_result = reward_db.get(RewardQuery.id == pending.reward_id)
        if not reward_result:
            continue
        reward = Reward.from_dict(reward_result)

        # Look up child details
        child_result = child_db.get(ChildQuery.id == pending.child_id)
        if not child_result:
            continue
        child = Child.from_dict(child_result)

        # Create response object
        response = PendingRewardResponse(
            _id=pending.id,
            child_id=child.id,
            child_name=child.name,
            child_image_id=child.image_id,
            reward_id=reward.id,
            reward_name=reward.name,
            reward_image_id=reward.image_id
        )
        reward_responses.append(response.to_dict())

    return jsonify({'rewards': reward_responses, 'count': len(reward_responses), 'list_type': 'notification'}), 200

