"""Useful functions for blinkpy."""

import json
import random
import logging
import time
import secrets
import re
from asyncio import sleep
from calendar import timegm
from functools import wraps
from getpass import getpass
import aiofiles
import dateutil.parser
from blinkpy.helpers import constants as const


_LOGGER = logging.getLogger(__name__)


async def json_load(file_name):
    """Load json credentials from file."""
    try:
        async with aiofiles.open(file_name, "r") as json_file:
            test = await json_file.read()
            data = json.loads(test)
        return data
    except FileNotFoundError:
        _LOGGER.error("Could not find %s", file_name)
    except json.decoder.JSONDecodeError:
        _LOGGER.error("File %s has improperly formatted json", file_name)
    return None


async def json_save(data, file_name):
    """Save data to file location."""
    async with aiofiles.open(file_name, "w") as json_file:
        await json_file.write(json.dumps(data, indent=4))


def json_dumps(json_in, indent=2):
    """Return a well formated json string."""
    return json.dumps(json_in, indent=indent)


def gen_uid(size, uid_format=False):
    """Create a random string."""
    if uid_format:
        token = (
            f"BlinkCamera_{secrets.token_hex(4)}-"
            f"{secrets.token_hex(2)}-{secrets.token_hex(2)}-"
            f"{secrets.token_hex(2)}-{secrets.token_hex(6)}"
        )
    else:
        token = secrets.token_hex(size)
    return token


def time_to_seconds(timestamp):
    """Convert TIMESTAMP_FORMAT time to seconds."""
    try:
        dtime = dateutil.parser.isoparse(timestamp)
    except ValueError:
        _LOGGER.error("Incorrect timestamp format for conversion: %s.", timestamp)
        return False
    return timegm(dtime.timetuple())


def get_time(time_to_convert=None):
    """Create blink-compatible timestamp."""
    if time_to_convert is None:
        time_to_convert = time.time()
    return time.strftime(const.TIMESTAMP_FORMAT, time.gmtime(time_to_convert))


def merge_dicts(dict_a, dict_b):
    """Merge two dictionaries into one."""
    duplicates = [val for val in dict_a if val in dict_b]
    if duplicates:
        _LOGGER.warning(
            ("Duplicates found during merge: %s. " "Renaming is recommended."),
            duplicates,
        )
    return {**dict_a, **dict_b}


def prompt_login_data(data):
    """Prompt user for username and password."""
    if data["username"] is None:
        data["username"] = input("Username:")
    if data["password"] is None:
        data["password"] = getpass("Password:")

    return data


def prompt_2fa_data(data):
    """Prompt user for 2FA code."""
    if data["2fa_code"] is None:
        data["2fa_code"] = input("2FA Code (if applicable):")

    return data


def validate_login_data(data):
    """Check for missing keys."""
    data["uid"] = data.get("uid", gen_uid(const.SIZE_UID, uid_format=True))
    data["device_id"] = data.get("device_id", const.DEVICE_ID)

    return data


def local_storage_clip_url_template():
    """Return URL template for local storage clip download location."""
    return (
        "/api/v1/accounts/$account_id/networks/$network_id/sync_modules/$sync_id"
        "/local_storage/manifest/$manifest_id/clip/request/$clip_id"
    )


def backoff_seconds(retry=0, default_time=1):
    """Calculate number of seconds to back off for retry."""
    return default_time * 2**retry + random.uniform(0, 1)


def to_alphanumeric(name):
    """Convert name to one with only alphanumeric characters."""
    return re.sub(r"\W+", "", name)


class BlinkException(Exception):
    """Class to throw general blink exception."""

    def __init__(self, errcode):
        """Initialize BlinkException."""
        super().__init__()
        self.errid = errcode[0]
        self.message = errcode[1]


class BlinkAuthenticationException(BlinkException):
    """Class to throw authentication exception."""


class BlinkURLHandler:
    """Class that handles Blink URLS."""

    def __init__(self, region_id):
        """Initialize the urls."""
        if region_id is None:
            raise TypeError
        self.subdomain = f"rest-{region_id}"
        self.base_url = f"https://{self.subdomain}.{const.BLINK_URL}"
        self.home_url = f"{self.base_url}/homescreen"
        self.event_url = f"{self.base_url}/events/network"
        self.network_url = f"{self.base_url}/network"
        self.networks_url = f"{self.base_url}/networks"
        self.video_url = f"{self.base_url}/api/v2/videos"
        _LOGGER.debug("Setting base url to %s.", self.base_url)


class Throttle:
    """Class for throttling api calls."""

    def __init__(self, seconds=10):
        """Initialize throttle class."""
        self.throttle_time = seconds
        self.last_call = 0

    def __call__(self, method):
        """Throttle caller method."""

        @wraps(method)
        async def wrapper(*args, **kwargs):
            """Wrap that checks for throttling."""
            force = kwargs.get("force", False)
            now = int(time.time())
            last_call_delta = now - self.last_call
            if force or last_call_delta > self.throttle_time:
                self.last_call = now
            else:
                self.last_call = now + last_call_delta
                await sleep(self.throttle_time - last_call_delta)

            return await method(*args, **kwargs)

        return wrapper
