from time import time
from typing import Dict

from werkzeug.security import gen_salt

from fittrackee.users.models import User

from .exceptions import InvalidOAuth2Scopes
from .models import OAuth2Client

VALID_SCOPES = [
    "application:write",
    "equipments:read",
    "equipments:write",
    "follow:read",
    "follow:write",
    "geocode:read",
    "notifications:read",
    "notifications:write",
    "profile:read",
    "profile:write",
    "reports:read",
    "reports:write",
    "users:read",
    "users:write",
    "workouts:read",
    "workouts:write",
]


def check_scope(scope: str) -> str:
    """
    Verify if provided scope is valid.
    """
    if not isinstance(scope, str) or not scope:
        raise InvalidOAuth2Scopes()

    valid_scopes = []
    scopes = scope.split()
    for value in scopes:
        if value in VALID_SCOPES:
            valid_scopes.append(value)

    if not valid_scopes:
        raise InvalidOAuth2Scopes()

    return " ".join(valid_scopes)


def create_oauth2_client(metadata: Dict, user: User) -> OAuth2Client:
    """
    Create OAuth2 client for 3rd-party applications.

    Only Authorization Code Grant with 'client_secret_post' as method
    is supported.
    Code challenge can be used if provided on authorization.
    """
    client_metadata = {
        "client_name": metadata["client_name"],
        "client_description": metadata.get("client_description"),
        "client_uri": metadata["client_uri"],
        "redirect_uris": metadata["redirect_uris"],
        "scope": check_scope(metadata["scope"]),
        "grant_types": ["authorization_code", "refresh_token"],
        "response_types": ["code"],
        "token_endpoint_auth_method": "client_secret_post",  # nosec
    }
    client_id = gen_salt(24)
    client_id_issued_at = int(time())
    client = OAuth2Client(
        client_id=client_id,
        client_id_issued_at=client_id_issued_at,
        user_id=user.id,
    )
    client.set_client_metadata(client_metadata)
    client.client_secret = gen_salt(48)
    return client
