#
# Bitcoin Safe
# Copyright (C) 2024 Andreas Griffin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of version 3 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see https://www.gnu.org/licenses/gpl-3.0.html
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


import logging
import os
import platform
import shutil
import tempfile
from pathlib import Path
from typing import Any, Dict, List, Optional

from bitcoin_safe_lib.async_tools.loop_in_thread import LoopInThread, MultipleStrategy
from packaging import version
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtWidgets import QHBoxLayout, QWidget

from bitcoin_safe.gui.qt.downloader import Downloader, DownloadThread
from bitcoin_safe.gui.qt.notification_bar import NotificationBar
from bitcoin_safe.gui.qt.util import svg_tools

from ... import __version__
from ...html_utils import html_f
from ...signals import SignalsMin, TypedPyQtSignalNo
from ...signature_manager import (
    Asset,
    GitHubAssetDownloader,
    KnownGPGKeys,
    SignatureVerifyer,
)
from .util import Message, MessageType, set_margins

logger = logging.getLogger(__name__)


class UpdateNotificationBar(NotificationBar):
    signal_on_success: TypedPyQtSignalNo = pyqtSignal()  # type: ignore

    key = KnownGPGKeys.andreasgriffin

    def __init__(
        self,
        signals_min: SignalsMin,
        loop_in_thread: LoopInThread,
        proxies: Dict | None,
        parent=None,
    ) -> None:
        self.proxies = proxies
        super().__init__(
            text="",
            optional_button_text="",
            callback_optional_button=self.check,
            has_close_button=True,
            parent=parent,
        )
        self.loop_in_thread = loop_in_thread
        self.signals_min = signals_min
        refresh_icon = svg_tools.get_QIcon("bi--arrow-clockwise.svg")
        self.optionalButton.setIcon(refresh_icon)

        self.verifyer = SignatureVerifyer(list_of_known_keys=[self.key], proxies=self.proxies)
        self.assets: List[Asset] = []
        self.setVisible(False)

        self.download_container = QWidget()
        self.download_container_layout = QHBoxLayout(self.download_container)

        set_margins(
            self.download_container_layout,
            {
                Qt.Edge.TopEdge: 1,
                Qt.Edge.BottomEdge: 5,
            },
        )
        self.download_container_layout.setSpacing(self.download_container_layout.spacing() // 2)
        self.add_styled_widget(self.download_container)

        self.refresh()
        self.signals_min.language_switch.connect(self.refresh)

    def get_asset_tag(self) -> Optional[str]:
        if self.assets:
            tag = self.assets[-1].tag
            return tag
        else:
            return None

    def is_new_version_available(self) -> bool:
        tag = self.get_asset_tag()
        if not tag:
            return False
        return version.parse(tag) > version.parse(__version__)

    def refresh(self) -> None:
        self.optionalButton.setText(self.tr("Check for Update"))

        # clear layout
        while self.download_container_layout.count():
            if (layout_item := self.download_container_layout.takeAt(0)) and (
                _widget := layout_item.widget()
            ):
                _widget.close()

        self.download_container.setVisible(bool(self.assets))
        if self.assets:
            if self.is_new_version_available():
                self.icon_label.setText(
                    self.tr("New version available {tag}").format(tag=self.get_asset_tag())
                )
                self.optionalButton.setVisible(False)
                self.setVisible(True)

                for asset in self.assets:
                    downloader = Downloader(
                        url=asset.url, destination_dir=tempfile.gettempdir(), proxies=self.proxies
                    )
                    downloader.finished.connect(self.on_download_finished)
                    self.download_container_layout.addWidget(downloader)
            else:
                self.icon_label.setText(self.tr("You have already the newest version."))
                self.optionalButton.setVisible(True)
        else:
            self.icon_label.setText(self.tr("No update found"))
            self.optionalButton.setVisible(True)

    def on_download_finished(self, download_thread: DownloadThread) -> None:
        sig_file_path = self.verifyer.get_signature_from_web(download_thread.filename)
        if not sig_file_path:
            Message(self.tr("Could not verify the download. Please try again later."))
            self.refresh()
            self.setVisible(False)
            return

        destination = self.get_download_folder()
        was_signature_verified = None

        was_signature_verified = self.verifyer.verify_signature(
            download_thread.filename, expected_public_key=self.key
        )
        if not was_signature_verified:
            Message(self.tr("Signature doesn't match!!! Please try again."), type=MessageType.Error)
            self.refresh()
            self.setVisible(False)
            return

        self.icon_label.setText(html_f(self.tr("Signature verified."), color="green", bf=True))

        # overwrite the download_thread.filename so the show-button still works
        download_thread.filename = self.move_and_overwrite(download_thread.filename, destination)
        if sig_file_path:
            self.move_and_overwrite(sig_file_path, destination)

    @staticmethod
    def move_and_overwrite(source: Path, destination: Path) -> Path:
        # convert destination to destination with filename
        if destination.is_dir():
            destination = destination / source.name

        if os.path.exists(destination):
            os.remove(destination)
        shutil.move(source, destination)
        return destination

    @staticmethod
    def get_download_folder() -> Path:
        return Path.home() / "Downloads"

    def get_filtered_assets(self, assets: List[Asset]) -> List[Asset]:
        filtered_assets: List[Asset] = []
        for asset in assets:
            if platform.system() == "Windows" and not any(
                [asset.name.endswith(ending) for ending in ["exe", "msi"]]
            ):
                continue
            elif platform.system() == "Linux" and not any(
                [
                    asset.name.endswith(ending)
                    for ending in ["AppImage", "deb", "rpm", "flatpak", "snap", "pkg.tar.zst"]
                ]
            ):
                continue
            elif platform.system() == "Darwin" and not any(
                [asset.name.endswith(ending) for ending in ["dmg"]]
            ):
                continue

            # check if the asset can be recognized
            if not self.key.get_tag_if_mine(asset.name):
                continue

            filtered_assets.append(asset)
        return filtered_assets

    def check(self) -> None:
        async def do() -> Any:
            return GitHubAssetDownloader(self.key.repository, proxies=self.proxies).get_assets_latest()

        def on_done(result) -> None:
            pass

        def on_success(assets: List[Asset]) -> None:
            # filter the assets, by recognized and for the platform

            self.assets = self.get_filtered_assets(assets)
            self.refresh()
            self.signal_on_success.emit()

        def on_error(packed_error_info) -> None:
            logger.error(f"error in fetching update info {packed_error_info}")

        self.loop_in_thread.run_task(
            do(),
            on_done=on_done,
            on_success=on_success,
            on_error=on_error,
            key=f"{id(self)}lazy_load_qr",
            multiple_strategy=MultipleStrategy.CANCEL_OLD_TASK,
        )

    def check_and_make_visible(self) -> None:
        self.check()
        self.setVisible(True)
