import base64
import os
import re
from datetime import datetime, timedelta
from typing import TypeVar

import jwt
from pkg_resources import get_distribution

from .exceptions import RSAKeyNotFoundError

ISO_8601_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
ID_NAME_REGEX = re.compile("^[a-zA-Z-_][a-zA-Z-_0-9]*$")
BASE64_REGEX = re.compile(
    "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
)

KEYS_DIR = os.path.join(os.environ["HOME"], ".scalade")

Base64Str = TypeVar("Base64Str", bound=str)


def parse_dt(date_str):
    return datetime.strptime(date_str, ISO_8601_FORMAT)


def format_dt(dt):
    return dt.strftime(ISO_8601_FORMAT)


def decode_b64str(body: Base64Str) -> bytes:
    return base64.b64decode(body.encode())


def bytes_to_b64str(_bytes: bytes) -> Base64Str:
    return base64.b64encode(_bytes).decode()  # type: ignore


def ensure_rsa_keys(func):
    """Reads RSA private/public key files and sets variables in environment."""

    def wrapper(*args, **kwargs):
        try:
            with open(os.path.join(KEYS_DIR, "key.pem"), "r") as p_file:
                os.environ["SCALADE_PRIVATE_KEY"] = p_file.read()
            with open(os.path.join(KEYS_DIR, "key.pub"), "r") as pub_file:
                os.environ["SCALADE_PUBLIC_KEY"] = pub_file.read()
        except Exception:  # pragma: no cover
            raise RSAKeyNotFoundError()
        return func(*args, **kwargs)

    return wrapper


@ensure_rsa_keys
def encode_scalade_token(payload):
    private_key = os.environ["SCALADE_PRIVATE_KEY"]
    return jwt.encode(payload, private_key, algorithm="RS256")


@ensure_rsa_keys
def decode_scalade_token(token: str) -> dict:
    public_key = os.environ["SCALADE_PUBLIC_KEY"]
    try:
        decoded_token = jwt.decode(token, public_key, algorithms=["RS256"])
    except jwt.exceptions.DecodeError:
        raise jwt.exceptions.DecodeError(
            "Error decoding function Token. "
            "This may be key missmatch or wrong key."
        )
    except jwt.exceptions.ExpiredSignatureError:
        raise jwt.exceptions.ExpiredSignatureError(
            "The function Token has been expired"
        )
    return decoded_token


def generate_token_payload(job_uuid: str, ttl=7200):
    gen_time = datetime.now()
    exp_time = gen_time + timedelta(hours=ttl / 3600)
    return {
        "job_uuid": job_uuid,
        "iat": int(gen_time.timestamp()),
        "exp": int(exp_time.timestamp()),
    }


def get_pckg_dist_version_num() -> str:
    dist = get_distribution("scalade")
    version = dist.version
    return version[0]
