from collections import defaultdict
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

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

from api.error_codes import ErrorCodes
from api.utils import get_validated_user_id, send_event_for_current_user
from db.child_overrides import delete_override, get_override
from db.db import child_db, pending_confirmations_db, routine_db, users_db
from db.routine_extensions import delete_extension_for_child_routine, get_extension_for_child_routine
from db.routine_items import get_items_for_routine
from db.routine_schedules import delete_schedule, get_schedule
from events.types.child_routine_confirmation import ChildRoutineConfirmation
from events.types.child_routines_set import ChildRoutinesSet
from events.types.event import Event
from events.types.event_types import EventType
from models.child import Child
from models.pending_confirmation import PendingConfirmation
from models.routine import Routine
from utils.digest_token import create_action_token
from utils.push_sender import send_push_to_user

child_routine_api = Blueprint('child_routine_api', __name__)


def _get_user_timezone(user_id: str) -> str | None:
    user = users_db.get(Query().id == user_id)
    if not user:
        return None
    return user.get('timezone')


def _get_user_today_local(user_id: str) -> tuple[str, str | None]:
    tz_str = _get_user_timezone(user_id)
    try:
        now_local = datetime.now(ZoneInfo(tz_str)) if tz_str else datetime.now(timezone.utc)
    except Exception:
        tz_str = None
        now_local = datetime.now(timezone.utc)
    return now_local.strftime('%Y-%m-%d'), tz_str


def _is_iso_timestamp_on_local_day(timestamp: str | None, local_day: str, tz_str: str | None) -> bool:
    if not timestamp:
        return False
    try:
        normalized = timestamp.replace('Z', '+00:00')
        parsed = datetime.fromisoformat(normalized)
        if parsed.tzinfo is None:
            parsed = parsed.replace(tzinfo=timezone.utc)
        try:
            tz = ZoneInfo(tz_str) if tz_str else timezone.utc
        except Exception:
            tz = timezone.utc
        return parsed.astimezone(tz).strftime('%Y-%m-%d') == local_day
    except (TypeError, ValueError):
        return False


def _is_epoch_timestamp_on_local_day(epoch_ts, local_day: str, tz_str: str | None) -> bool:
    if epoch_ts is None:
        return False
    try:
        tz = ZoneInfo(tz_str) if tz_str else timezone.utc
    except Exception:
        tz = timezone.utc
    try:
        return datetime.fromtimestamp(float(epoch_ts), tz).strftime('%Y-%m-%d') == local_day
    except (TypeError, ValueError, OSError):
        return False


class ChildRoutine:
    def __init__(self, name, points, image_id, _id):
        self.id = _id
        self.name = name
        self.points = points
        self.image_id = image_id

    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'points': self.points,
            'image_id': self.image_id,
        }


def _validate_child_for_user(child_id: str, user_id: str):
    child_q = Query()
    result = child_db.search((child_q.id == child_id) & (child_q.user_id == user_id))
    return Child.from_dict(result[0]) if result else None


def _resolve_routine_for_user(routine_id: str, user_id: str):
    routine_q = Query()
    routine_result = routine_db.get(
        (routine_q.id == routine_id) & ((routine_q.user_id == user_id) | (routine_q.user_id == None))
    )
    return Routine.from_dict(routine_result) if routine_result else None


@child_routine_api.route('/child/<id>/assign-routine', methods=['POST'])
def assign_routine_to_child(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    data = request.get_json() or {}
    routine_id = data.get('routine_id')
    if not routine_id:
        return jsonify({'error': 'routine_id is required', 'code': ErrorCodes.MISSING_FIELD}), 400

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    routine = _resolve_routine_for_user(routine_id, user_id)
    if not routine:
        return jsonify({'error': 'Routine not found'}), 404

    routine_ids = list(child.routines)
    if routine_id not in routine_ids:
        routine_ids.append(routine_id)
        child_db.update({'routines': routine_ids}, Query().id == id)

    send_event_for_current_user(
        Event(EventType.CHILD_ROUTINES_SET.value, ChildRoutinesSet(id, routine_ids))
    )
    return jsonify({'message': f'Routine {routine_id} assigned to {child.name}.'}), 200


@child_routine_api.route('/child/<id>/remove-routine', methods=['POST'])
def remove_routine_from_child(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    data = request.get_json() or {}
    routine_id = data.get('routine_id')
    if not routine_id:
        return jsonify({'error': 'routine_id is required', 'code': ErrorCodes.MISSING_FIELD}), 400

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    routine_ids = list(child.routines)
    if routine_id not in routine_ids:
        return jsonify({'error': 'Routine not assigned to child', 'code': ErrorCodes.ENTITY_NOT_ASSIGNED}), 400

    routine_ids.remove(routine_id)
    child_db.update({'routines': routine_ids}, Query().id == id)

    override = get_override(id, routine_id)
    if override and override.entity_type == 'routine':
        delete_override(id, routine_id)

    delete_schedule(id, routine_id)
    delete_extension_for_child_routine(id, routine_id)

    pending_q = Query()
    pending_confirmations_db.remove(
        (pending_q.child_id == id) & (pending_q.entity_id == routine_id) &
        (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
    )

    send_event_for_current_user(
        Event(EventType.CHILD_ROUTINES_SET.value, ChildRoutinesSet(id, routine_ids))
    )
    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, routine_id, ChildRoutineConfirmation.OPERATION_RESET)
        )
    )
    return jsonify({'message': f'Routine {routine_id} removed from {child.name}.'}), 200


@child_routine_api.route('/child/<id>/set-routines', methods=['PUT'])
def set_child_routines(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    data = request.get_json() or {}
    routine_ids = data.get('routine_ids')
    if not isinstance(routine_ids, list):
        return jsonify({'error': 'routine_ids must be a list'}), 400

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    routine_q = Query()
    valid_ids = []
    for rid in dict.fromkeys(routine_ids):
        if routine_db.get((routine_q.id == rid) & ((routine_q.user_id == user_id) | (routine_q.user_id == None))):
            valid_ids.append(rid)

    old_ids = set(child.routines)
    new_ids = set(valid_ids)
    unassigned_ids = old_ids - new_ids

    pending_q = Query()
    for rid in unassigned_ids:
        override = get_override(id, rid)
        if override and override.entity_type == 'routine':
            delete_override(id, rid)

        delete_schedule(id, rid)
        delete_extension_for_child_routine(id, rid)

        pending_confirmations_db.remove(
            (pending_q.child_id == id) & (pending_q.entity_id == rid) &
            (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
        )

    child_db.update({'routines': valid_ids}, Query().id == id)
    send_event_for_current_user(Event(EventType.CHILD_ROUTINES_SET.value, ChildRoutinesSet(id, valid_ids)))

    return jsonify({'message': f'Routines set for child {id}.', 'routine_ids': valid_ids, 'count': len(valid_ids)}), 200


@child_routine_api.route('/child/<id>/list-routines', methods=['GET'])
def list_child_routines(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    routine_q = Query()
    pending_q = Query()
    today_local, tz_str = _get_user_today_local(user_id)

    child_routines = []
    for rid in child.routines:
        routine_record = routine_db.get((routine_q.id == rid) & ((routine_q.user_id == user_id) | (routine_q.user_id == None)))
        if not routine_record:
            continue

        routine = Routine.from_dict(routine_record)
        override = get_override(id, rid)
        custom_value = override.custom_value if override and override.entity_type == 'routine' else None

        cr = ChildRoutine(routine.name, routine.points, routine.image_id, routine.id)
        cr_dict = cr.to_dict()
        if custom_value is not None:
            cr_dict['custom_value'] = custom_value

        schedule = get_schedule(id, rid)
        cr_dict['schedule'] = schedule.to_dict() if schedule else None

        extension = get_extension_for_child_routine(id, rid)
        cr_dict['extension_date'] = extension.date if extension else None

        items = get_items_for_routine(rid)
        cr_dict['items'] = [item.to_dict() for item in items]

        pending = pending_confirmations_db.get(
            (pending_q.child_id == id) & (pending_q.entity_id == rid) &
            (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
        )
        if pending:
            status = pending.get('status')
            approved_at = pending.get('approved_at')
            created_at = pending.get('created_at')
            confirmation_id = pending.get('id')

            if status == 'approved' and _is_iso_timestamp_on_local_day(approved_at, today_local, tz_str):
                cr_dict['pending_status'] = 'approved'
                cr_dict['approved_at'] = approved_at
                cr_dict['pending_confirmation_id'] = confirmation_id
            elif status == 'pending' and _is_epoch_timestamp_on_local_day(created_at, today_local, tz_str):
                cr_dict['pending_status'] = 'pending'
                cr_dict['approved_at'] = None
                cr_dict['pending_confirmation_id'] = confirmation_id
            else:
                pending_id = pending.get('id')
                if pending_id:
                    pending_confirmations_db.remove(pending_q.id == pending_id)
                cr_dict['pending_status'] = None
                cr_dict['approved_at'] = None
                cr_dict['pending_confirmation_id'] = None
        else:
            cr_dict['pending_status'] = None
            cr_dict['approved_at'] = None
            cr_dict['pending_confirmation_id'] = None

        child_routines.append(cr_dict)

    return jsonify({'routines': child_routines}), 200


@child_routine_api.route('/child/<id>/list-assignable-routines', methods=['GET'])
def list_assignable_routines(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    assigned_ids = set(child.routines)
    all_routines = [r for r in routine_db.all() if r and r.get('id') and r.get('id') not in assigned_ids]

    name_to_routines = defaultdict(list)
    for routine in all_routines:
        name_to_routines[routine.get('name')].append(routine)

    filtered_routines = []
    for _, routines in name_to_routines.items():
        user_routines = [r for r in routines if r.get('user_id') is not None]
        if len(user_routines) == 0:
            filtered_routines.append(routines[0])
        elif len(user_routines) == 1:
            filtered_routines.append(user_routines[0])
        else:
            filtered_routines.extend(user_routines)

    assignable = [
        ChildRoutine(r.get('name'), r.get('points'), r.get('image_id'), r.get('id')).to_dict()
        for r in filtered_routines
    ]
    return jsonify({'routines': assignable, 'count': len(assignable)}), 200


@child_routine_api.route('/child/<id>/confirm-routine', methods=['POST'])
def confirm_routine(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    data = request.get_json() or {}
    routine_id = data.get('routine_id')
    if not routine_id:
        return jsonify({'error': 'routine_id is required', 'code': ErrorCodes.MISSING_FIELD}), 400

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    if routine_id not in child.routines:
        return jsonify({'error': 'Routine not assigned to child', 'code': ErrorCodes.ENTITY_NOT_ASSIGNED}), 400

    routine = _resolve_routine_for_user(routine_id, user_id)
    if not routine:
        return jsonify({'error': 'Routine not found'}), 404

    pending_q = Query()
    existing = pending_confirmations_db.get(
        (pending_q.child_id == id) & (pending_q.entity_id == routine_id) &
        (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
    )
    if existing:
        today_local, tz_str = _get_user_today_local(user_id)
        if existing.get('status') == 'pending':
            if _is_epoch_timestamp_on_local_day(existing.get('created_at'), today_local, tz_str):
                return jsonify({'error': 'Routine already pending confirmation', 'code': 'ROUTINE_ALREADY_PENDING'}), 400
            pending_id = existing.get('id')
            if pending_id:
                pending_confirmations_db.remove(pending_q.id == pending_id)
        if existing.get('status') == 'approved':
            approved_at = existing.get('approved_at', '')
            if _is_iso_timestamp_on_local_day(approved_at, today_local, tz_str):
                return jsonify({'error': 'Routine already completed today', 'code': 'ROUTINE_ALREADY_COMPLETED'}), 400
            pending_id = existing.get('id')
            if pending_id:
                pending_confirmations_db.remove(pending_q.id == pending_id)

    confirmation = PendingConfirmation(
        child_id=id,
        entity_id=routine_id,
        entity_type='routine',
        user_id=user_id,
    )
    pending_confirmations_db.insert(confirmation.to_dict())

    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, routine_id, ChildRoutineConfirmation.OPERATION_PENDING)
        )
    )

    push_user = users_db.get(Query().id == user_id)
    if push_user and push_user.get('push_notifications_enabled', True):
        try:
            approve_token = create_action_token(user_id, id, routine_id, 'routine', 'approve')
            deny_token = create_action_token(user_id, id, routine_id, 'routine', 'deny')
            push_payload = {
                'type': 'routine_confirmed',
                'title': 'Routine Pending',
                'body': f'{child.name} completed {routine.name}',
                'user_id': user_id,
                'child_id': id,
                'child_name': child.name,
                'entity_id': routine_id,
                'entity_type': 'routine',
                'entity_name': routine.name,
                'approve_token': approve_token.id,
                'deny_token': deny_token.id,
            }
            send_push_to_user(user_id, push_payload)
        except Exception:
            pass

    return jsonify({'message': f'Routine {routine.name} confirmed by {child.name}.', 'confirmation_id': confirmation.id}), 200


@child_routine_api.route('/child/<id>/cancel-routine-confirmation', methods=['POST'])
def cancel_routine_confirmation(id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    data = request.get_json() or {}
    routine_id = data.get('routine_id')
    if not routine_id:
        return jsonify({'error': 'routine_id is required', 'code': ErrorCodes.MISSING_FIELD}), 400

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    pending_q = Query()
    existing = pending_confirmations_db.get(
        (pending_q.child_id == id) & (pending_q.entity_id == routine_id) &
        (pending_q.entity_type == 'routine') & (pending_q.status == 'pending') &
        (pending_q.user_id == user_id)
    )
    if not existing:
        return jsonify({'error': 'No pending confirmation found', 'code': 'PENDING_NOT_FOUND'}), 400

    pending_confirmations_db.remove(
        (pending_q.child_id == id) & (pending_q.entity_id == routine_id) &
        (pending_q.entity_type == 'routine') & (pending_q.status == 'pending') &
        (pending_q.user_id == user_id)
    )

    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, routine_id, ChildRoutineConfirmation.OPERATION_RESET)
        )
    )
    return jsonify({'message': 'Routine confirmation cancelled.'}), 200


@child_routine_api.route('/child/<id>/approve-routine/<confirmation_id>', methods=['POST'])
def approve_routine(id, confirmation_id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    pending_q = Query()
    confirmation = pending_confirmations_db.get(
        (pending_q.id == confirmation_id) & (pending_q.child_id == id) &
        (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
    )
    if not confirmation:
        return jsonify({'error': 'Pending confirmation not found', 'code': 'PENDING_NOT_FOUND'}), 404

    if confirmation.get('status') != 'pending':
        return jsonify({'error': 'Confirmation is already resolved', 'code': 'ALREADY_RESOLVED'}), 400

    routine_id = confirmation.get('entity_id')
    routine = _resolve_routine_for_user(routine_id, user_id)
    if not routine:
        return jsonify({'error': 'Routine not found'}), 404

    override = get_override(id, routine_id)
    points_value = override.custom_value if override and override.entity_type == 'routine' else routine.points

    new_points = max(0, child.points + points_value)
    child_db.update({'points': new_points}, Query().id == id)

    approved_at = datetime.now(timezone.utc).isoformat()
    pending_confirmations_db.update(
        {'status': 'approved', 'approved_at': approved_at},
        pending_q.id == confirmation_id
    )

    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, routine_id, ChildRoutineConfirmation.OPERATION_APPROVED)
        )
    )

    return jsonify({
        'message': f'Routine {routine.name} approved for {child.name}.',
        'points': new_points,
        'id': child.id,
    }), 200


@child_routine_api.route('/child/<id>/reject-routine/<confirmation_id>', methods=['POST'])
def reject_routine(id, confirmation_id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    pending_q = Query()
    confirmation = pending_confirmations_db.get(
        (pending_q.id == confirmation_id) & (pending_q.child_id == id) &
        (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
    )
    if not confirmation:
        return jsonify({'error': 'Pending confirmation not found', 'code': 'PENDING_NOT_FOUND'}), 404

    if confirmation.get('status') != 'pending':
        return jsonify({'error': 'Confirmation is already resolved', 'code': 'ALREADY_RESOLVED'}), 400

    pending_confirmations_db.update({'status': 'rejected', 'approved_at': None}, pending_q.id == confirmation_id)

    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, confirmation.get('entity_id'), ChildRoutineConfirmation.OPERATION_REJECTED)
        )
    )

    return jsonify({'message': 'Routine confirmation rejected.'}), 200


@child_routine_api.route('/child/<id>/reset-routine/<confirmation_id>', methods=['POST'])
def reset_routine(id, confirmation_id):
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    pending_q = Query()
    confirmation = pending_confirmations_db.get(
        (pending_q.id == confirmation_id) & (pending_q.child_id == id) &
        (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
    )
    if not confirmation:
        return jsonify({'error': 'Pending confirmation not found', 'code': 'PENDING_NOT_FOUND'}), 404

    routine_id = confirmation.get('entity_id')
    pending_confirmations_db.remove(pending_q.id == confirmation_id)

    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, routine_id, ChildRoutineConfirmation.OPERATION_RESET)
        )
    )
    return jsonify({'message': 'Routine reset to available.'}), 200


@child_routine_api.route('/child/<id>/trigger-routine', methods=['POST'])
def trigger_child_routine(id):
    """Parent-triggered routine confirmation — directly awards points."""
    user_id = get_validated_user_id()
    if not user_id:
        return jsonify({'error': 'Unauthorized', 'code': ErrorCodes.UNAUTHORIZED}), 401

    data = request.get_json() or {}
    routine_id = data.get('routine_id')
    if not routine_id:
        return jsonify({'error': 'routine_id is required', 'code': ErrorCodes.MISSING_FIELD}), 400

    child = _validate_child_for_user(id, user_id)
    if not child:
        return jsonify({'error': 'Child not found', 'code': ErrorCodes.CHILD_NOT_FOUND}), 404

    if routine_id not in child.routines:
        return jsonify({'error': 'Routine not assigned to child', 'code': ErrorCodes.ENTITY_NOT_ASSIGNED}), 400

    routine = _resolve_routine_for_user(routine_id, user_id)
    if not routine:
        return jsonify({'error': 'Routine not found'}), 404

    # Check for override
    override = get_override(id, routine_id)
    points_value = override.custom_value if override and override.entity_type == 'routine' else routine.points

    # Award points
    new_points = max(0, child.points + points_value)
    child_db.update({'points': new_points}, Query().id == id)

    # Create an approved pending confirmation so it shows as completed in the routine list
    pending_q = Query()
    existing = pending_confirmations_db.get(
        (pending_q.child_id == id) & (pending_q.entity_id == routine_id) &
        (pending_q.entity_type == 'routine') & (pending_q.user_id == user_id)
    )
    if existing:
        today_local, tz_str = _get_user_today_local(user_id)
        # Remove old confirmation if it exists and is not from today
        if existing.get('status') == 'approved' and _is_iso_timestamp_on_local_day(existing.get('approved_at'), today_local, tz_str):
            return jsonify({'error': 'Routine already completed today', 'code': 'ROUTINE_ALREADY_COMPLETED'}), 400
        pending_id = existing.get('id')
        if pending_id:
            pending_confirmations_db.remove(pending_q.id == pending_id)

    confirmation = PendingConfirmation(
        child_id=id,
        entity_id=routine_id,
        entity_type='routine',
        user_id=user_id,
        status='approved',
        approved_at=datetime.now(timezone.utc).isoformat()
    )
    pending_confirmations_db.insert(confirmation.to_dict())

    send_event_for_current_user(
        Event(
            EventType.CHILD_ROUTINE_CONFIRMATION.value,
            ChildRoutineConfirmation(id, routine_id, ChildRoutineConfirmation.OPERATION_APPROVED)
        )
    )

    return jsonify({
        'message': f'Routine {routine.name} awarded to {child.name}.',
        'points': new_points,
        'id': child.id,
    }), 200
