# v6

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any

APP_NAME = "Discord Client"
APP_VERSION = "1.0.0"

START_URL = "https://discord.com/app"

WINDOW_WIDTH = 1400
WINDOW_HEIGHT = 900

LOG_LEVEL = logging.INFO

BASE_DIR = Path.home() / ".discord-client"

DATABASE_PATH = BASE_DIR / "client.db"
SETTINGS_FILE = BASE_DIR / "settings.json"

PROFILES_DIR = BASE_DIR / "profiles"
DOWNLOADS_DIR = BASE_DIR / "downloads"
LOGS_DIR = BASE_DIR / "logs"

THEMES_DIR = BASE_DIR / "themes"
EXTENSIONS_DIR = BASE_DIR / "extensions"

EXPORTS_DIR = BASE_DIR / "exports"
HAR_DIR = EXPORTS_DIR / "har"

DEFAULT_PROFILE = "default"

NETWORK_RETENTION = 100_000
NETWORK_BATCH_SIZE = 100
NETWORK_FLUSH_INTERVAL = 1.0


DISCORD_TELEMETRY_BLOCKS = [
    "sentry.io",
    "www.sentry.io",
    "crash.discord.com",
    "telemetry.discord.com",
    "api.statsig.com",
    "segment.io",
    "cdn.segment.com",
]

DEFAULT_PROXY = {
    "enabled": False,
    "proxy_type": "http",
    "host": "",
    "port": 0,
    "username": "",
    "password": "",
}

DEFAULT_TRAY = {
    "enabled": True,
    "minimize_to_tray": True,
    "close_to_tray": True,
}

DEFAULT_DEVTOOLS = {
    "enabled": True,
    "capture_requests": True,
    "capture_responses": True,
    "capture_bodies": False,
    "auto_open": False,
}

DEFAULT_PERMISSIONS = {
    "microphone": False,
    "camera": False,
    "notifications": False,
    "clipboard": False,
}

DEFAULT_SETTINGS: dict[str, Any] = {
    "active_profile": DEFAULT_PROFILE,
    "window_width": WINDOW_WIDTH,
    "window_height": WINDOW_HEIGHT,
    "download_directory": str(DOWNLOADS_DIR),
    "blocked_hosts": list(
        DISCORD_TELEMETRY_BLOCKS
    ),
    "network_logging": True,
    "enable_cdp": False,
    "enable_vencord": False,
    "permissions": DEFAULT_PERMISSIONS,
    "proxy": DEFAULT_PROXY,
    "tray": DEFAULT_TRAY,
    "devtools": DEFAULT_DEVTOOLS,
    "blocklist_mode": "pro",
    "disabled_blocklists": [],
}

FEATURE_PERMISSION_MAP = {
    "MediaAudioCapture": "microphone",
    "MediaVideoCapture": "camera",
    "MediaAudioVideoCapture": "camera",
    "Notifications": "notifications",
    "ClipboardReadWrite": "clipboard",
    "ClipboardSanitizedWrite": "clipboard",
}

PLUGIN_FILE_PATTERN = "*.plugin.js"

PLUGIN_MAX_SIZE = 5 * 1024 * 1024

PLUGIN_CRASH_LIMIT = 5

PLUGIN_AUTOLOAD = True

PLUGIN_ALLOWED_EXTENSIONS = {
    ".plugin.js",
}

PLUGIN_ALLOWED_SCHEMES = {
    "https",
}

PLUGIN_BLOCKED_PATTERNS = {
    "child_process.exec(",
    "child_process.spawn(",
    "powershell.exe",
    "cmd.exe /c",
    "process.exec(",
}
PLUGIN_PERMISSIONS = {
    "storage",
    "dom",
    "network",
    "websocket",
    "notifications",
    "clipboard",
}
PROFILE_EXPORT_VERSION = 1

PROFILE_EXPORT_NAME = (
    "profilebundle"
)

# v1
BLOCKLISTS_DIR = (
    BASE_DIR
    / "blocklists"
)

BLOCKLIST_CACHE_DIR = (
    BLOCKLISTS_DIR
    / "cache"
)
def blocklists_dir(
) -> Path:
    BLOCKLISTS_DIR.mkdir(
        parents=True,
        exist_ok=True,
    )
    return BLOCKLISTS_DIR


# v1
def blocklist_cache_dir(
) -> Path:
    BLOCKLIST_CACHE_DIR.mkdir(
        parents=True,
        exist_ok=True,
    )
    return BLOCKLIST_CACHE_DIR

def ensure_directories() -> None:
    for directory in (
        BASE_DIR,
        PROFILES_DIR,
        DOWNLOADS_DIR,
        LOGS_DIR,
        THEMES_DIR,
        EXTENSIONS_DIR,
        EXPORTS_DIR,
        HAR_DIR,
        BLOCKLISTS_DIR,
        BLOCKLIST_CACHE_DIR,
    ):
        directory.mkdir(
            parents=True,
            exist_ok=True,
        )

def merge_dict(
    defaults: dict,
    current: dict,
) -> dict:
    merged = dict(defaults)

    for key, value in current.items():
        if (
            key in merged
            and isinstance(
                merged[key],
                dict,
            )
            and isinstance(
                value,
                dict,
            )
        ):
            merged[key] = merge_dict(
                merged[key],
                value,
            )
        else:
            merged[key] = value

    return merged


def _normalize_proxy(
    settings: dict[str, Any],
) -> None:
    proxy = settings.get(
        "proxy",
        {},
    )

    if not isinstance(
        proxy,
        dict,
    ):
        settings["proxy"] = dict(
            DEFAULT_PROXY
        )
        return

    if (
        "type" in proxy
        and "proxy_type"
        not in proxy
    ):
        proxy["proxy_type"] = proxy.pop(
            "type"
        )

    settings["proxy"] = merge_dict(
        DEFAULT_PROXY,
        proxy,
    )


def _normalize_permissions(
    settings: dict[str, Any],
) -> None:
    permissions = settings.get(
        "permissions",
        {},
    )

    if not isinstance(
        permissions,
        dict,
    ):
        permissions = {}

    settings["permissions"] = merge_dict(
        DEFAULT_PERMISSIONS,
        permissions,
    )


def _normalize_blocked_hosts(
    settings: dict[str, Any],
) -> None:
    hosts = {
        str(host)
        .strip()
        .lower()
        for host in settings.get(
            "blocked_hosts",
            [],
        )
        if str(host).strip()
    }

    hosts.update(
        DISCORD_TELEMETRY_BLOCKS
    )

    settings["blocked_hosts"] = sorted(
        hosts
    )


def load_settings() -> dict[str, Any]:
    ensure_directories()

    if not SETTINGS_FILE.exists():
        settings = merge_dict(
            {},
            DEFAULT_SETTINGS,
        )

        save_settings(
            settings
        )

        return settings

    try:
        loaded = json.loads(
            SETTINGS_FILE.read_text(
                encoding="utf-8"
            )
        )

        if not isinstance(
            loaded,
            dict,
        ):
            raise ValueError

        if (
            "active_profile"
            not in loaded
            and "active_account"
            in loaded
        ):
            loaded[
                "active_profile"
            ] = loaded[
                "active_account"
            ]

        settings = merge_dict(
            DEFAULT_SETTINGS,
            loaded,
        )

        if not settings.get(
            "active_profile"
        ):
            settings[
                "active_profile"
            ] = DEFAULT_PROFILE

        settings[
            "download_directory"
        ] = str(
            settings.get(
                "download_directory"
            )
            or DOWNLOADS_DIR
        )

        _normalize_proxy(
            settings
        )

        _normalize_permissions(
            settings
        )

        _normalize_blocked_hosts(
            settings
        )

        return settings

    except Exception:
        settings = merge_dict(
            {},
            DEFAULT_SETTINGS,
        )

        save_settings(
            settings
        )

        return settings


def save_settings(
    settings: dict[str, Any],
) -> None:
    ensure_directories()

    _normalize_proxy(
        settings
    )

    _normalize_permissions(
        settings
    )

    _normalize_blocked_hosts(
        settings
    )

    SETTINGS_FILE.write_text(
        json.dumps(
            settings,
            indent=2,
            sort_keys=True,
            ensure_ascii=False,
        ),
        encoding="utf-8",
    )


def profile_root(
    profile_name: str,
) -> Path:
    profile_name = (
        str(profile_name).strip()
        or DEFAULT_PROFILE
    )

    root = (
        PROFILES_DIR
        / profile_name
    )

    root.mkdir(
        parents=True,
        exist_ok=True,
    )

    return root


def profile_paths(
    profile_name: str,
) -> tuple[Path, Path]:
    root = profile_root(
        profile_name
    )

    storage = root / "storage"
    cache = root / "cache"

    storage.mkdir(
        parents=True,
        exist_ok=True,
    )

    cache.mkdir(
        parents=True,
        exist_ok=True,
    )

    return (
        storage,
        cache,
    )


def profile_theme_dir(
    profile_name: str,
) -> Path:
    path = (
        THEMES_DIR
        / profile_name
    )

    path.mkdir(
        parents=True,
        exist_ok=True,
    )

    return path


def profile_extension_dir(
    profile_name: str,
) -> Path:
    path = (
        EXTENSIONS_DIR
        / profile_name
    )

    path.mkdir(
        parents=True,
        exist_ok=True,
    )

    return path


def profile_vencord_dir(
    profile_name: str,
) -> Path:
    path = (
        profile_root(
            profile_name
        )
        / "vencord"
    )

    path.mkdir(
        parents=True,
        exist_ok=True,
    )

    for child in (
        "plugins",
        "themes",
        "logs",
    ):
        (
            path / child
        ).mkdir(
            parents=True,
            exist_ok=True,
        )

    return path


def profile_plugin_dir(
    profile_name: str,
) -> Path:
    root = (
        profile_vencord_dir(
            profile_name
        )
        / "plugins"
    )

    root.mkdir(
        parents=True,
        exist_ok=True,
    )

    return root