from functools import wraps
from typing import Any, Dict, List, Optional, Tuple
from flask import request, jsonify

import datetime
import random
import redis
import jwt

from app.core.config import settings


redis_client = redis.Redis(
    host=settings.REDIS_HOST,
    port=settings.REDIS_PORT,
    password=settings.REDIS_PASSWORD or None,
    db=0,
    decode_responses=True,   # so you get strings instead of bytes
)


hub_validation_codes = {}


def require_jwt(roles=None, match_id_from_token=None):
    def decorator(view_function):
        @wraps(view_function)
        def decorated_function(*args, **kwargs):
            auth_header = request.headers.get('Authorization')
            if not auth_header or not auth_header.startswith('Bearer '):
                return jsonify({'error': 'Pristup zabranjen'}), 401

            token = auth_header.split(' ')[1]
            try:
                decoded_token = jwt.decode(
                    token, settings.SECRET_KEY, algorithms=["HS256"])
            except jwt.ExpiredSignatureError:
                return jsonify({'error': 'Sesija istekla'}), 401
            except jwt.InvalidTokenError:
                return jsonify({'error': 'Nevalidan token'}), 401

            user_role = decoded_token.get('user_role')
            if roles:
                allowed = [roles] if isinstance(roles, str) else roles
                if user_role not in allowed:
                    return jsonify({'error': 'Pristup zabranjen'}), 403

            # ID ownership check
            if match_id_from_token:
                token_id = decoded_token.get('user_id')
                route_id = kwargs.get(match_id_from_token)

                if token_id is None or str(token_id) != str(route_id):
                    return jsonify({'error': 'Pristup zabranjen'}), 403

            request.user = decoded_token
            return view_function(*args, **kwargs)
        return decorated_function
    return decorator


# Generates and stores a 4-digit code
def create_code(user_id: int) -> str:
    # Random 4-digit code
    validation_code = str(random.randint(1000, 9999))

    # Current time
    time_created_at = datetime.datetime.now(datetime.timezone.utc)

    # Create new code data
    new_code_data = {
        'code': validation_code,
        'time_created_at': time_created_at,
    }

    # Overwrite previous data with new code data
    hub_validation_codes[user_id] = new_code_data

    return validation_code


# Checks if the code is valid
def check_validation_code(
        submitted_code: str, user_id: int) -> Tuple[Dict[str, Any], List[int]]:

    if user_id not in hub_validation_codes:
        return ({'error': 'Kod ne postoji'}, [404])

    db_code = hub_validation_codes[user_id]['code']
    time_created_at = hub_validation_codes[user_id]['time_created_at']

    # Check if the code is expired (valid for 5 minutes)
    if datetime.datetime.now(
            datetime.timezone.utc) > time_created_at + datetime.timedelta(minutes=5):
        del hub_validation_codes[user_id]
        return ({'error': 'Kod je istekao'}, [410])

    # Check if the code matches
    if submitted_code != db_code:
        return ({'error': 'Kod je neispravan'}, [401])

    # Code is valid, remove it after use
    del hub_validation_codes[user_id]

    return ({}, [200])


# Generates a JWT token
def generate_token(
        user_id: int,
        user_role: str,
        expiration_minutes: int) -> str:
    expiration_time = datetime.datetime.now(
        datetime.timezone.utc) + datetime.timedelta(expiration_minutes)

    payload = {
        'user_id': user_id,
        'user_role': user_role,
        'exp': expiration_time.timestamp()  # Expiration timestamp
    }

    return jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')


# Generates access and refresh tokens
def generate_tokens(user_role: str,
                    user_id: int) -> Tuple[Dict[str,
                                                Any],
                                           List[int],
                                           Optional[Dict[str,
                                                         Any]]]:
    access_token = generate_token(
        user_id, user_role, expiration_minutes=60)  # 1-hour token
    refresh_token = generate_token(
        user_id, user_role, expiration_minutes=10080)  # 7-day token

    try:
        # Store Refresh token in Redis (expires after 7 days)
        redis_client.setex(
            f"refresh_token:{user_id}",
            604800,
            refresh_token.encode("utf-8"))
    except Exception as e:
        return ({'error': 'Neuspešno čuvanje sesije u "Redis-u"'}, [500], None)

    # Set tokens
    tokens = {
        'access_token': access_token,
        'refresh_token': refresh_token
    }

    return ({}, [200], tokens)


def generate_new_access_token(
        refresh_token: str) -> Tuple[Dict[str, Any], List[int], Optional[Dict[str, Any]]]:
    try:
        # Decode the refresh token
        decoded = jwt.decode(
            refresh_token,
            settings.SECRET_KEY,
            algorithm=["HS256"])
        user_id = decoded['user_id']
        user_role = decoded['user_role']

        # Check if the refresh token exists in Redis
        stored_token = redis_client.get(f"refresh_token:{user_id}")
        if not stored_token or refresh_token != stored_token.decode('utf-8'):
            return ({'error': 'Nevalidan refresh token'}, [401], None)

        # Generate new Access & Refresh Tokens
        new_access_token = generate_token(
            user_id, user_role, expiration_minutes=60)  # 1-hour token
        new_refresh_token = generate_token(
            user_id, user_role, expiration_minutes=10080)  # 7-day token

        # Update refresh token expiration in Redis (reset 7-day timer)
        redis_client.setex(
            f"refresh_token:{user_id}",
            604800,
            new_refresh_token.encode("utf-8"))  # 7 days

        # Send new tokens in cookies
        tokens = {
            'access_token': new_access_token,
            'refresh_token': new_refresh_token
        }

        return ({}, 200, tokens)

    except jwt.ExpiredSignatureError:
        return ({'error': 'Sesija istekla'}, [401], None)
    except jwt.InvalidTokenError:
        return ({'error': 'Nevalidan refresh token'}, [401], None)


def remove_refresh_token(
        refresh_token: str) -> Tuple[Dict[str, Any], List[int], Optional[Dict[str, Any]]]:
    try:
        # Decode the Refresh Token to get user_id
        decoded = jwt.decode(
            refresh_token,
            settings.SECRET_KEY,
            algorithms=["HS256"])
        user_id = decoded['user_id']

        # Remove Refresh Token from Redis
        redis_client.delete(f"refresh_token:{user_id}")

    except jwt.ExpiredSignatureError:
        pass  # Token je već istekao, nema potrebe da brišemo
    except jwt.InvalidTokenError:
        return ({'error': 'Nevalidan token'}, [401], None)

    return ({}, [200], None)
