# Copyright (C) 2006 Gustavo J. A. M. Carneiro <gjcarneiro AT gmail.com>
#                    Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
#                    Julien Pivotto <roidelapluie AT gmail.com>
# Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de>
# Copyright (c) 2009 Thorsten Glaser <t.glaser AT tarent.de>
#
# This file is part of Gajim.
#
# SPDX-License-Identifier: GPL-3.0-only

from __future__ import annotations

from typing import cast

import logging

import keyring
import keyring.backend
import keyring.core
import keyring.errors

from gajim.common import app
from gajim.common.util.version import package_version

__all__ = [
    "delete_password",
    "get_password",
    "init",
    "is_keyring_available",
    "save_password",
]

log = logging.getLogger("gajim.c.passwords")


class Interface:
    def __init__(self):
        self.backend = cast(keyring.backend.KeyringBackend, None)
        self._is_keyring_available = False

    @property
    def is_keyring_available(self):
        return self._is_keyring_available

    def init(self) -> None:
        backends = keyring.backend.get_all_keyring()
        for backend in backends:
            log.info("Found keyring backend: %s", backend)

        if app.settings.get("enable_keepassxc_integration") and package_version(
            "keyring>=23.8.1"
        ):
            _keyring = keyring.get_keyring()
            self.backend = _keyring.with_properties(scheme="KeePassXC")
        else:
            self.backend = keyring.get_keyring()
        log.info("Select %s backend", self.backend)

        self._is_keyring_available = keyring.core.recommended(self.backend)  # type: ignore


class SecretPasswordStorage:
    """
    Store password using Keyring
    """

    @staticmethod
    def save_password(account_name: str, password: str) -> bool:
        if not is_keyring_available():
            log.warning(
                "No recommended keyring backend available.Passwords cannot be stored."
            )
            return False

        account_jid = app.get_jid_from_account(account_name)

        try:
            log.info("Save password to keyring")
            _interface.backend.set_password("gajim", account_jid, password)  # type: ignore
            return True
        except Exception:
            log.exception("Save password failed")
            return False

    @staticmethod
    def get_password(account_name: str) -> str | None:
        if not is_keyring_available():
            return

        account_jid = app.get_jid_from_account(account_name)

        log.info("Request password from keyring for %s", account_jid)

        try:
            # For security reasons remove clear-text password
            ConfigPasswordStorage.delete_password(account_name)
            password = _interface.backend.get_password("gajim", account_jid)
        except Exception:
            log.exception("Request password failed")
            return

        if password is not None:
            return password

        # Migration from account name to account jid
        try:
            password = _interface.backend.get_password("gajim", account_name)
        except Exception:
            log.exception("Request password failed")
            return

        if password is not None:
            result = SecretPasswordStorage.save_password(account_name, password)
            if not result:
                log.error("Password migration failed")

        return password

    @staticmethod
    def delete_password(account_name: str) -> None:
        if not is_keyring_available():
            return

        log.info("Remove password from keyring")

        account_jid = app.get_jid_from_account(account_name)

        keyring_errors = (
            keyring.errors.InitError,
            keyring.errors.KeyringLocked,
            keyring.errors.PasswordDeleteError,
        )
        try:
            _interface.backend.delete_password("gajim", account_name)
        except keyring_errors:
            pass
        except Exception:
            pass

        try:
            return _interface.backend.delete_password("gajim", account_jid)
        except keyring_errors as error:
            log.warning("Removing password failed: %s", error)
        except Exception:
            log.exception("Removing password failed")


class ConfigPasswordStorage:
    """
    Store password directly in Gajim's config
    """

    @staticmethod
    def get_password(account_name: str) -> str:
        return app.settings.get_account_setting(account_name, "password")

    @staticmethod
    def save_password(account_name: str, password: str) -> bool:
        app.settings.set_account_setting(account_name, "password", password)
        return True

    @staticmethod
    def delete_password(account_name: str) -> None:
        app.settings.set_account_setting(account_name, "password", "")


class MemoryPasswordStorage:
    """
    Store password in memory
    """

    _passwords: dict[str, str] = {}

    def get_password(self, account_name: str) -> str:
        return self._passwords.get(account_name, "")

    def save_password(self, account_name: str, password: str) -> bool:
        self._passwords[account_name] = password
        return True

    def delete_password(self, account_name: str) -> None:
        self._passwords[account_name] = ""


def init() -> None:
    _interface.init()


def is_keyring_available() -> bool:
    return _interface.is_keyring_available


def get_password(account_name: str) -> str | None:
    if not app.settings.get_account_setting(account_name, "savepass"):
        return MemoryPasswordStorage().get_password(account_name)

    if app.settings.get("use_keyring"):
        return SecretPasswordStorage.get_password(account_name)
    return ConfigPasswordStorage.get_password(account_name)


def save_password(account_name: str, password: str) -> bool:
    if not app.settings.get_account_setting(account_name, "savepass"):
        return MemoryPasswordStorage().save_password(account_name, password)

    if app.settings.get("use_keyring"):
        return SecretPasswordStorage.save_password(account_name, password)
    return ConfigPasswordStorage.save_password(account_name, password)


def delete_password(account_name: str) -> None:
    if app.settings.get("use_keyring"):
        return SecretPasswordStorage.delete_password(account_name)
    return ConfigPasswordStorage.delete_password(account_name)


_interface = Interface()
