import time
from sqlite3 import IntegrityError
from PyQL3.shell import PyConnection
from base64 import b64decode
from pathlib import Path
from json import load
from Crypto.Cipher import AES
from win32crypt import CryptUnprotectData

CONV = \
    {'host_key': 'domain', 'name': 'name', 'value': 'value', 'path': 'path',
     'expires_utc': 'expires', 'is_secure': 'secure', 'is_httponly': 'httpOnly'}
DFLT = \
    {'creation_utc': '1577826000',
     'is_persistent': '1', }
REQS = \
    ('name', 'value', 'domain', 'path', 'expires')


def decrypt(key, encrypted_value):
    """
    RU: Расшифровывает зашифрованное значение с помощью предоставленного ключа.
    EN: Decrypts the encrypted value using the provided key.

    Args:
        key (bytes): The decryption key.
        encrypted_value (bytes): The value to be decrypted.

    Returns:
        str: The decrypted value as a string.
    """
    if not isinstance(encrypted_value, bytes):
        return None
    if not encrypted_value.startswith(b'v10'):
        return None
    iv = encrypted_value[3:15]
    encrypted_value = encrypted_value[15:]
    cipher = AES.new(key, AES.MODE_GCM, iv)
    return cipher.decrypt(encrypted_value)[:-16].decode()


class Biscuit:
    """
    RU: Класс для взаимодействия с куками в браузере пользователя.
    EN: A class to interact with cookies in a user's browser.

    Attributes:
        _user_data_dir (Path): The path to the user's data directory.
        _key (bytes): The decryption key.
        _conn (PyConnection): The connection to the database.
    """

    def __init__(self, user_data_dir, profile='Default'):
        """
        RU: Создает все необходимые атрибуты для объекта Biscuit.
        EN: Constructs all the necessary attributes for the Biscuit object.

        Args:
            user_data_dir (str): The path to the user's data directory.
            profile (str, optional): The user's profile. Defaults to 'Default'.
        """
        self._user_data_dir = Path(user_data_dir)

        local_state = self._user_data_dir / 'Local State'
        with open(local_state, "r", encoding='latin-1') as file:
            local_state = load(file)
        key = b64decode(local_state["os_crypt"]["encrypted_key"])[5:]
        self._key = CryptUnprotectData(key, None, None, None, 0)[1]

        cookies_path = self._user_data_dir / profile / 'Network' / 'Cookies'
        if not cookies_path.exists():
            raise FileNotFoundError(cookies_path)
        self._conn = PyConnection(cookies_path)

    def get(self, where=None, order=None, limit=None, offset=None):
        """
        RU: Извлекает файлы cookie из базы данных.
        EN: Retrieves cookies from the database.

        Args:
            where (str, optional): The WHERE clause for the SQL query. Defaults to None.
            order (str, optional): The ORDER BY clause for the SQL query. Defaults to None.
            limit (int, optional): The LIMIT clause for the SQL query. Defaults to None.
            offset (int, optional): The OFFSET clause for the SQL query. Defaults to None.

        Returns:
            list: A list of dictionaries representing the cookies.
        """
        rows = self._conn.get('cookies').rows
        columns = list(CONV.keys()) + ['encrypted_value']

        cookies = []
        for cookie in rows.deserialize(columns, where, order, limit, offset):
            decrypted_value = decrypt(self._key, cookie[-1])
            cookie = dict(zip(CONV.values(), cookie[:-1]))
            if decrypted_value:
                cookie['value'] = decrypted_value
            if not cookie.get('value', None):
                continue
            cookies.append(cookie)
        return cookies

    def add(self, cookies):
        """
        RU: Добавляет файлы cookie в базу данных.
        EN: Adds cookies to the database.

        Args:
            cookies (list): A list of dictionaries representing the cookies.
        """
        table = self._conn.get('cookies')
        columns = table.columns.ids
        for cookie in cookies:
            if not all(item in cookie for item in REQS):
                continue
            values = \
                [cookie[CONV[item]] if item in CONV.keys()
                 else DFLT.get(item, '') for item in columns]
            try:
                table.rows.insert(values=tuple(values))
            except IntegrityError:
                pass
        self._conn.commit()
