import os
from datetime import datetime, timezone
from functools import wraps

import jwt
from flask import request, jsonify, current_app, make_response

from app.api import api_bp
from app.extensions import db
from app.models.user import User


def create_access_token(user_id):
    exp = datetime.now(timezone.utc) + current_app.config['JWT_ACCESS_TOKEN_EXPIRES']
    payload = {
        'user_id': user_id,
        'type': 'access',
        'exp': exp,
        'iat': datetime.now(timezone.utc),
    }
    return jwt.encode(payload, current_app.config['JWT_SECRET_KEY'], algorithm='HS256')


def create_refresh_token(user_id):
    exp = datetime.now(timezone.utc) + current_app.config['JWT_REFRESH_TOKEN_EXPIRES']
    payload = {
        'user_id': user_id,
        'type': 'refresh',
        'exp': exp,
        'iat': datetime.now(timezone.utc),
    }
    return jwt.encode(payload, current_app.config['JWT_SECRET_KEY'], algorithm='HS256')


def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        auth_header = request.headers.get('Authorization', '')
        if auth_header.startswith('Bearer '):
            token = auth_header[7:]

        # Fallback: token as query parameter (for direct browser downloads)
        if not token:
            token = request.args.get('token', '')

        if not token:
            return jsonify({'error': 'Token fehlt'}), 401

        try:
            payload = jwt.decode(token, current_app.config['JWT_SECRET_KEY'],
                                 algorithms=['HS256'])
            if payload.get('type') != 'access':
                return jsonify({'error': 'Falscher Token-Typ'}), 401
            user = db.session.get(User, payload['user_id'])
            if not user or not user.is_active:
                return jsonify({'error': 'Benutzer nicht gefunden oder deaktiviert'}), 401
        except jwt.ExpiredSignatureError:
            return jsonify({'error': 'Token abgelaufen'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Ungueltiger Token'}), 401

        request.current_user = user
        return f(*args, **kwargs)
    return decorated


def admin_required(f):
    @wraps(f)
    @token_required
    def decorated(*args, **kwargs):
        if request.current_user.role != 'admin':
            return jsonify({'error': 'Admin-Berechtigung erforderlich'}), 403
        return f(*args, **kwargs)
    return decorated


@api_bp.route('/auth/registration-status', methods=['GET'])
def registration_status():
    """Check if public registration is allowed."""
    from app.models.settings import AppSettings
    is_first_user = User.query.count() == 0
    public_registration = AppSettings.get_bool('public_registration', default=True)
    return jsonify({
        'allowed': is_first_user or public_registration,
        'is_first_user': is_first_user,
    }), 200


@api_bp.route('/auth/register', methods=['POST'])
def register():
    from app.models.settings import AppSettings

    is_first_user = User.query.count() == 0

    # Check invite token (works even if public registration is off)
    invite_token = request.args.get('invite') or (request.get_json() or {}).get('invite_token')
    valid_invite = False
    if invite_token:
        from app.models.settings import AppSettings as _S
        stored = _S.get(f'invite_{invite_token}', '')
        if stored == 'valid':
            valid_invite = True

    # Check if registration is allowed
    if not is_first_user and not valid_invite and not AppSettings.get_bool('public_registration', default=True):
        return jsonify({'error': 'Oeffentliche Registrierung ist deaktiviert'}), 403

    data = request.get_json()
    if not data:
        return jsonify({'error': 'Keine Daten gesendet'}), 400

    username = data.get('username', '').strip()
    password = data.get('password', '')
    email = data.get('email', '').strip() or None

    if not username or not password:
        return jsonify({'error': 'Benutzername und Passwort erforderlich'}), 400

    if len(username) < 3:
        return jsonify({'error': 'Benutzername muss mindestens 3 Zeichen lang sein'}), 400

    if len(password) < 8:
        return jsonify({'error': 'Passwort muss mindestens 8 Zeichen lang sein'}), 400

    if User.query.filter_by(username=username).first():
        return jsonify({'error': 'Benutzername bereits vergeben'}), 409

    if email and User.query.filter_by(email=email).first():
        return jsonify({'error': 'Email-Adresse bereits vergeben'}), 409

    # First user becomes admin
    is_first_user = User.query.count() == 0

    user = User(
        username=username,
        email=email,
        role='admin' if is_first_user else 'user',
        master_key_salt=os.urandom(32),
    )
    user.set_password(password)

    db.session.add(user)
    db.session.commit()

    # Invalidate invite token if used
    if valid_invite and invite_token:
        AppSettings.set(f'invite_{invite_token}', 'used')

    access_token = create_access_token(user.id)
    refresh_token = create_refresh_token(user.id)

    response = make_response(jsonify({
        'user': user.to_dict(include_email=True),
        'access_token': access_token,
    }))

    response.set_cookie(
        'refresh_token', refresh_token,
        httponly=True, secure=request.is_secure,
        samesite='Lax',
        max_age=int(current_app.config['JWT_REFRESH_TOKEN_EXPIRES'].total_seconds()),
    )

    return response, 201


@api_bp.route('/auth/login', methods=['POST'])
def login():
    data = request.get_json()
    if not data:
        return jsonify({'error': 'Keine Daten gesendet'}), 400

    username = data.get('username', '').strip()
    password = data.get('password', '')

    if not username or not password:
        return jsonify({'error': 'Benutzername und Passwort erforderlich'}), 400

    user = User.query.filter_by(username=username).first()
    if not user or not user.check_password(password):
        return jsonify({'error': 'Ungueltige Anmeldedaten'}), 401

    if not user.is_active:
        return jsonify({'error': 'Konto deaktiviert'}), 403

    access_token = create_access_token(user.id)
    refresh_token = create_refresh_token(user.id)

    import base64
    response = make_response(jsonify({
        'user': user.to_dict(include_email=True),
        'access_token': access_token,
        'master_key_salt': base64.b64encode(user.master_key_salt).decode() if user.master_key_salt else None,
    }))

    response.set_cookie(
        'refresh_token', refresh_token,
        httponly=True, secure=request.is_secure,
        samesite='Lax',
        max_age=int(current_app.config['JWT_REFRESH_TOKEN_EXPIRES'].total_seconds()),
    )

    return response, 200


@api_bp.route('/auth/refresh', methods=['POST'])
def refresh():
    refresh_token = request.cookies.get('refresh_token')
    if not refresh_token:
        return jsonify({'error': 'Refresh-Token fehlt'}), 401

    try:
        payload = jwt.decode(refresh_token, current_app.config['JWT_SECRET_KEY'],
                             algorithms=['HS256'])
        if payload.get('type') != 'refresh':
            return jsonify({'error': 'Falscher Token-Typ'}), 401
        user = db.session.get(User, payload['user_id'])
        if not user or not user.is_active:
            return jsonify({'error': 'Benutzer nicht gefunden'}), 401
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'Refresh-Token abgelaufen'}), 401
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Ungueltiger Token'}), 401

    access_token = create_access_token(user.id)
    return jsonify({'access_token': access_token}), 200


@api_bp.route('/auth/logout', methods=['POST'])
def logout():
    response = make_response(jsonify({'message': 'Abgemeldet'}))
    response.delete_cookie('refresh_token')
    return response, 200


@api_bp.route('/auth/me', methods=['GET'])
@token_required
def me():
    import base64
    user = request.current_user
    data = user.to_dict(include_email=True)
    data['master_key_salt'] = base64.b64encode(user.master_key_salt).decode() if user.master_key_salt else None
    data['email_account_count'] = user.email_accounts.count()
    return jsonify(data), 200
