"""User API routes."""
from flask import Blueprint, g, jsonify, request
from pydantic import ValidationError

from app.routes.auth import (
    auth_required,
    can_read_user_data,
    require_roles,
    require_self_or_admin,
)
from app.models.user import User
from app.repositories import (
    create_user,
    delete_user,
    find_user_by_email,
    find_user_by_id,
    find_user_by_username,
    find_users_by_ids,
    find_users_by_role,
    update_user,
)

users_bp = Blueprint('users_api', __name__)


def _public_user_payload(user: User) -> dict:
    """Return a safe user payload without password hash."""
    payload = user.model_dump(by_alias=True, mode='json')
    payload.pop('passwdHash', None)
    return payload


@users_bp.route('/users', methods=['POST'])
@auth_required
@require_roles('admin')
def create_user_endpoint():
    """Create a new user from JSON payload.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    requestBody:
        required: true
        content:
            application/json:
                schema:
                    $ref: '#/components/schemas/UserCreateRequest'
    responses:
        201:
            description: User created
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/UserPublic'
        400:
            description: Validation error
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        401:
            description: Unauthorized
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        403:
            description: Forbidden
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
    """
    payload = request.get_json(silent=True) or {}
    try:
        user = User.model_validate(payload)
    except ValidationError as e:
        return jsonify({'error': 'ValidationError', 'detail': str(e)}), 400

    created = create_user(user)
    return jsonify(_public_user_payload(created)), 201


@users_bp.route('/users/batch', methods=['POST'])
@auth_required
def batch_get_users_endpoint():
    """Fetch multiple users by their IDs in a single request.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    requestBody:
        required: true
        content:
            application/json:
                schema:
                    type: object
                    properties:
                        ids:
                            type: array
                            items:
                                type: string
                    required: [ids]
    responses:
        200:
            description: Array of user payloads
            content:
                application/json:
                    schema:
                        type: array
                        items:
                            $ref: '#/components/schemas/UserPublic'
        400:
            description: Missing ids field
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
    """
    data = request.get_json(silent=True) or {}
    ids = data.get('ids')
    if not ids or not isinstance(ids, list):
        return jsonify({'error': 'MissingField', 'field': 'ids'}), 400

    auth_user = g.get('auth_user') or {}
    requester_id = str(auth_user.get('sub', ''))
    requester_roles = set(auth_user.get('roles', []))

    # Non-admins can only fetch users from their own sharingInfo
    if 'admin' not in requester_roles:
        requester = find_user_by_id(requester_id)
        allowed_ids = {str(uid) for uid in (requester.sharingInfo or [])} if requester else set()
        ids = [uid for uid in ids if uid in allowed_ids]
        if not ids:
            return jsonify([])

    users = find_users_by_ids(ids)
    result = [_public_user_payload(user) for user in users]
    return jsonify(result)


@users_bp.route('/users/<user_id>', methods=['GET'])
@auth_required
def get_user_by_id_endpoint(user_id: str):
    """Get a user by their ID.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    parameters:
        - in: path
          name: user_id
          required: true
          schema:
            type: string
    responses:
        200:
            description: User found
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/UserPublic'
        401:
            description: Unauthorized
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        403:
            description: Forbidden
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        404:
            description: User not found
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
    """
    user = find_user_by_id(user_id)
    if not user:
        return jsonify({'error': 'NotFound'}), 404
    if not can_read_user_data(str(user.id), (str(x) for x in (user.sharingInfo or []))):
        return jsonify({'error': 'Forbidden', 'detail': 'Not allowed for this user'}), 403
    return jsonify(_public_user_payload(user))


@users_bp.route('/users/by-role', methods=['GET'])
@auth_required
def list_users_by_role_endpoint():
    """List users filtered by role.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    parameters:
        - in: query
          name: role
          required: true
          schema:
            type: string
            enum: [patient, practitioner, admin]
    responses:
        200:
            description: Users list
            content:
                application/json:
                    schema:
                        type: array
                        items:
                            $ref: '#/components/schemas/UserPublic'
        400:
            description: Missing role param
    """
    role = request.args.get('role')
    if not role:
        return jsonify({'error': 'MissingQueryParam', 'param': 'role'}), 400

    users = find_users_by_role(role)
    return jsonify([_public_user_payload(u) for u in users])


@users_bp.route('/users/by-email', methods=['GET'])
@auth_required
def get_user_by_email_endpoint():
    """Get a user by their email address.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    parameters:
        - in: query
          name: email
          required: true
          schema:
            type: string
    responses:
        200:
            description: User found
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/UserPublic'
        400:
            description: Missing email query parameter
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        401:
            description: Unauthorized
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        403:
            description: Forbidden
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        404:
            description: User not found
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
    """
    email = request.args.get('email')
    if not email:
        return jsonify({'error': 'MissingQueryParam', 'param': 'email'}), 400

    user = find_user_by_email(email)
    if not user:
        return jsonify({'error': 'NotFound'}), 404
    if not can_read_user_data(str(user.id), (str(x) for x in (user.sharingInfo or []))):
        return jsonify({'error': 'Forbidden', 'detail': 'Not allowed for this user'}), 403
    return jsonify(_public_user_payload(user))


@users_bp.route('/users/<user_id>', methods=['PATCH'])
@auth_required
@require_self_or_admin('user_id')
def update_user_endpoint(user_id: str):
    """Update a user's information.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    parameters:
        - in: path
          name: user_id
          required: true
          schema:
            type: string
    requestBody:
        required: true
        content:
            application/json:
                schema:
                    $ref: '#/components/schemas/UserUpdateRequest'
    responses:
        200:
            description: User updated
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/UserPublic'
        400:
            description: No updatable fields
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        401:
            description: Unauthorized
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        403:
            description: Forbidden
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
        404:
            description: User not found
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
    """
    updates = request.get_json(silent=True) or {}
    # Only allow known fields for partial updates.
    allowed = {
        'username', 'dispName', 'email', 'roles', 'active',
        'residentCountry', 'residentCity', 'bloodType', 'height', 'weight',
        'allergies', 'specialty', 'hospital', 'department', 'title', 'phoneNumber',
        'sharingInfo',
    }
    filtered_updates = {k: v for k, v in updates.items() if k in allowed}
    if not filtered_updates:
        return jsonify({'error': 'NoUpdatableFields'}), 400

    is_admin = 'admin' in set((g.get('auth_user') or {}).get('roles', []))
    restricted = {'roles', 'active'}
    if not is_admin and restricted.intersection(filtered_updates):
        return jsonify({'error': 'Forbidden', 'detail': 'Cannot modify privileged fields'}), 403

    if 'username' in filtered_updates:
        new_username = str(filtered_updates['username']).strip()
        if not new_username:
            return jsonify({'error': 'ValidationError', 'detail': 'username cannot be empty'}), 400
        existing_username_user = find_user_by_username(new_username)
        if existing_username_user and str(existing_username_user.id) != user_id:
            return jsonify({'error': 'UsernameAlreadyExists'}), 409
        filtered_updates['username'] = new_username

    updated = update_user(user_id, filtered_updates)
    if not updated:
        return jsonify({'error': 'NotFound'}), 404
    return jsonify(_public_user_payload(updated))


@users_bp.route('/users/<user_id>/tests', methods=['GET'])
@require_self_or_admin('user_id')
@auth_required
def get_user_tests_endpoint(user_id: str):
    """Get all tests for a user by their ID.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    parameters:
        - in: path
          name: user_id
          required: true
          schema:
            type: string
          description: The ID of the user to fetch tests for
    responses:
        200:
            description: Array of tests for the user.
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/TestResponseList'
        400:
            description: Invalid ID format
        404:
            description: User not found
    """
    user = find_user_by_id(user_id)
    if not user:
        return jsonify({'error': 'UserNotFound'}), 404

    try:
        import json
        from app.repositories.test_repository import find_by_patient_id
        tests = find_by_patient_id(user_id)
        return jsonify([json.loads(test.model_dump_json(by_alias=True)) for test in tests])
    except ValueError as e:
        return jsonify({'error': 'InvalidId', 'detail': str(e)}), 400


@users_bp.route('/users/<user_id>', methods=['DELETE'])
@auth_required
@require_self_or_admin('user_id')
def delete_user_endpoint(user_id: str):
    """Delete a user account.
    ---
    tags:
        - Users
    security:
        - BearerAuth: []
    parameters:
        - in: path
          name: user_id
          required: true
          schema:
            type: string
    responses:
        200:
            description: User deleted
        404:
            description: User not found
    """
    deleted = delete_user(user_id)
    if not deleted:
        return jsonify({'error': 'NotFound'}), 404
    return jsonify({'message': 'Account deleted successfully'})
