import subprocess
from contextlib import suppress
from pathlib import Path

from briefcase.commands import (
    BuildCommand,
    DevCommand,
    OpenCommand,
    PublishCommand,
    UpdateCommand,
)
from briefcase.config import BaseConfig
from briefcase.exceptions import BriefcaseCommandError
from briefcase.integrations.rcedit import RCEdit
from briefcase.integrations.windows_sdk import WindowsSDK
from briefcase.platforms.windows import (
    WindowsCreateCommand,
    WindowsMixin,
    WindowsPackageCommand,
    WindowsRunCommand,
)


class WindowsAppMixin(WindowsMixin):
    output_format = "app"
    packaging_root = Path("src")
    supports_external_packaging = True

    def project_path(self, app):
        return self.bundle_path(app)


class WindowsAppCreateCommand(WindowsAppMixin, WindowsCreateCommand):
    description = "Create and populate a Windows app."


class WindowsAppUpdateCommand(WindowsAppCreateCommand, UpdateCommand):
    description = "Update an existing Windows app."


class WindowsAppOpenCommand(WindowsAppMixin, OpenCommand):
    description = "Open the folder containing an existing Windows app."


class WindowsAppBuildCommand(WindowsAppMixin, BuildCommand):
    description = "Build a Windows app."

    def verify_tools(self):
        super().verify_tools()
        RCEdit.verify(tools=self.tools)
        # The Windows SDK is only needed if it has previously been used to sign
        # the binary and MSI; therefore, ignore if it isn't available since the
        # stub app should not have any signatures to remove in that case.
        with suppress(BriefcaseCommandError):
            WindowsSDK.verify(tools=self.tools)

    def build_app(self, app: BaseConfig, **kwargs):
        """Build the application.

        :param app: The config object for the app
        """
        self.console.info("Building App...", prefix=app.app_name)

        # Move the stub binary in to the final executable location
        unbuilt_binary_path = self.unbuilt_executable_path(app)

        # We can run this test on non-Windows platforms, but when we run on
        # non-windows platforms, the ".exe" suffix doesn't exist, so it doesn't
        # hit this branch. That's not actually a problem, as long as we *are*
        # hitting the branch under Windows.
        if unbuilt_binary_path.exists():  # pragma: no-cover-if-not-windows
            with self.console.wait_bar("Renaming stub binary..."):
                unbuilt_binary_path.rename(self.binary_executable_path(app))

        if hasattr(self.tools, "windows_sdk"):
            # If an app has been packaged and code signed previously, then the digital
            # signature on the app binary needs to be removed before re-building the app.
            # It is not safe to use RCEdit on signed binaries since it corrupts them.
            with self.console.wait_bar(
                "Removing any digital signatures from stub app..."
            ):
                try:
                    self.tools.subprocess.check_output(
                        [
                            self.tools.windows_sdk.signtool_exe,
                            "remove",
                            "-s",
                            self.binary_path(app).relative_to(self.bundle_path(app)),
                        ],
                        cwd=self.bundle_path(app),
                        quiet=1,
                    )
                except subprocess.CalledProcessError as e:
                    # Ignore this error from signtool since it is logged if the file
                    # is not currently signed
                    if "error: 0x00000057" not in e.stdout:
                        self.tools.subprocess.output_error(e)
                        raise BriefcaseCommandError(
                            f"""\
Failed to remove any existing digital signatures from the stub app.

Recreating the app layout may also help resolve this issue:

    $ briefcase create {self.platform} {self.output_format}

"""
                        ) from e

        with self.console.wait_bar("Setting stub app details..."):
            try:
                self.tools.subprocess.run(
                    [
                        self.tools.rcedit.rcedit_path,
                        self.binary_path(app).relative_to(self.bundle_path(app)),
                        "--set-version-string",
                        "CompanyName",
                        app.author,
                        # Although "FileDescription" sounds like it should be a... description,
                        # this is the label that appears as a grouping in the Task Manager
                        # when the application runs.
                        "--set-version-string",
                        "FileDescription",
                        app.formal_name,
                        "--set-version-string",
                        "FileVersion",
                        app.version,
                        "--set-version-string",
                        "InternalName",
                        app.module_name,
                        "--set-version-string",
                        "OriginalFilename",
                        self.binary_path(app).name,
                        "--set-version-string",
                        "ProductName",
                        app.formal_name,
                        "--set-version-string",
                        "ProductVersion",
                        app.version,
                        "--set-icon",
                        "icon.ico",
                    ],
                    check=True,
                    cwd=self.bundle_path(app),
                )
            except subprocess.CalledProcessError as e:
                raise BriefcaseCommandError(
                    f"""\
Unable to update details on stub app for {app.app_name}.

This may be caused by a virus scanner misidentifying the Briefcase build as malicious
activity. Try disabling your virus checker, and re-run briefcase build.
"""
                ) from e


class WindowsAppRunCommand(WindowsAppMixin, WindowsRunCommand):
    description = "Run a Windows app."


class WindowsAppDevCommand(WindowsAppMixin, DevCommand):
    description = "Run a Windows app in development mode."


class WindowsAppPackageCommand(WindowsAppMixin, WindowsPackageCommand):
    description = "Package a Windows app."


class WindowsAppPublishCommand(WindowsAppMixin, PublishCommand):
    description = "Publish a Windows app."


# Declare the briefcase command bindings
create = WindowsAppCreateCommand
update = WindowsAppUpdateCommand
open = WindowsAppOpenCommand
build = WindowsAppBuildCommand
run = WindowsAppRunCommand
package = WindowsAppPackageCommand
publish = WindowsAppPublishCommand
dev = WindowsAppDevCommand
