# Copyright (c) Mengning Software. 2023. All rights reserved.
#
# Super IDE licensed under GNU Affero General Public License v3 (AGPL-3.0) .
# You can use this software according to the terms and conditions of the AGPL-3.0.
# You may obtain a copy of AGPL-3.0 at:
#
#    https://www.gnu.org/licenses/agpl-3.0.txt
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the AGPL-3.0 for more details.

import json
import logging
import os

import click

from superide.exception import UserSideException
from superide.package.commands.install import package_install_cmd
from superide.package.commands.list import package_list_cmd
from superide.package.commands.search import package_search_cmd
from superide.package.commands.show import package_show_cmd
from superide.package.commands.uninstall import package_uninstall_cmd
from superide.package.commands.update import package_update_cmd
from superide.package.manager.platform import PlatformPackageManager
from superide.package.meta import PackageItem, PackageSpec
from superide.package.version import get_original_version
from superide.platform.exception import UnknownPlatform
from superide.platform.factory import PlatformFactory


@click.group(short_help="Platform manager", hidden=True)
def cli():
    pass


@cli.command("search", short_help="Search for development platform")
@click.argument("query", required=False)
@click.option("--json-output", is_flag=True)
@click.pass_context
def platform_search(ctx, query, json_output):
    if not json_output:
        click.secho(
            "\nWARNING: This command is deprecated and will be removed in "
            "the next releases. \nPlease use `pio pkg search` instead.\n",
            fg="yellow",
        )
        query = query or ""
        return ctx.invoke(package_search_cmd, query=f"type:platform {query}".strip())

    platforms = []
    for platform in _get_registry_platforms():
        if query == "all":
            query = ""
        search_data = json.dumps(platform)
        if query and query.lower() not in search_data.lower():
            continue
        platforms.append(
            _get_registry_platform_data(
                platform["name"], with_boards=False, expose_packages=False
            )
        )
    click.echo(json.dumps(platforms))
    return None


@cli.command("frameworks", short_help="List supported frameworks, SDKs")
@click.argument("query", required=False)
@click.option("--json-output", is_flag=True)
def platform_frameworks(query, json_output):
    if not json_output:
        click.secho(
            "\nWARNING: This command is deprecated and will be removed in "
            "the next releases. \nPlease visit https://docs.superide.org"
            "/en/latest/frameworks/index.html\n",
            fg="yellow",
        )
        return

    regclient = PlatformPackageManager().get_registry_client_instance()
    frameworks = []
    for framework in regclient.fetch_json_data(
        "get", "/v2/frameworks", x_cache_valid="1d"
    ):
        if query == "all":
            query = ""
        search_data = json.dumps(framework)
        if query and query.lower() not in search_data.lower():
            continue
        framework["homepage"] = "https://superide.org/frameworks/" + framework["name"]
        framework["platforms"] = [
            platform["name"]
            for platform in _get_registry_platforms()
            if framework["name"] in platform["frameworks"]
        ]
        frameworks.append(framework)

    frameworks = sorted(frameworks, key=lambda manifest: manifest["name"])
    click.echo(json.dumps(frameworks))


@cli.command("list", short_help="List installed development platforms")
@click.option("--json-output", is_flag=True)
@click.pass_context
def platform_list(ctx, json_output):
    if not json_output:
        click.secho(
            "\nWARNING: This command is deprecated and will be removed in "
            "the next releases. \nPlease use `pio pkg list` instead.\n",
            fg="yellow",
        )
        return ctx.invoke(package_list_cmd, **{"global": True, "only_platforms": True})

    platforms = []
    pm = PlatformPackageManager()
    for pkg in pm.get_installed():
        platforms.append(
            _get_installed_platform_data(pkg, with_boards=False, expose_packages=False)
        )

    platforms = sorted(platforms, key=lambda manifest: manifest["name"])
    click.echo(json.dumps(platforms))
    return None


@cli.command("show", short_help="Show details about development platform")
@click.argument("platform")
@click.option("--json-output", is_flag=True)
@click.pass_context
def platform_show(ctx, platform, json_output):  # pylint: disable=too-many-branches
    if not json_output:
        click.secho(
            "\nWARNING: This command is deprecated and will be removed in "
            "the next releases. \nPlease use `pio pkg show` instead.\n",
            fg="yellow",
        )
        return ctx.invoke(package_show_cmd, pkg_type="platform", spec=platform)

    data = _get_platform_data(platform)
    if not data:
        raise UnknownPlatform(platform)
    return click.echo(json.dumps(data))


@cli.command("install", short_help="Install new development platform")
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
@click.option("--with-package", multiple=True)
@click.option("--without-package", multiple=True)
@click.option("--skip-default-package", is_flag=True)
@click.option("--with-all-packages", is_flag=True)
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.option(
    "-f",
    "--force",
    is_flag=True,
    help="Reinstall/redownload dev/platform and its packages if exist",
)
@click.pass_context
def platform_install(  # pylint: disable=too-many-arguments
    ctx,
    platforms,
    with_package,
    without_package,
    skip_default_package,
    with_all_packages,
    silent,
    force,
):
    click.secho(
        "\nWARNING: This command is deprecated and will be removed in "
        "the next releases. \nPlease use `pio pkg install` instead.\n",
        fg="yellow",
    )
    ctx.invoke(
        package_install_cmd,
        **{
            "global": True,
            "platforms": platforms,
            "skip_dependencies": (
                not with_all_packages
                and (with_package or without_package or skip_default_package)
            ),
            "silent": silent,
            "force": force,
        },
    )


@cli.command("uninstall", short_help="Uninstall development platform")
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
@click.pass_context
def platform_uninstall(ctx, platforms):
    click.secho(
        "\nWARNING: This command is deprecated and will be removed in "
        "the next releases. \nPlease use `pio pkg uninstall` instead.\n",
        fg="yellow",
    )
    ctx.invoke(
        package_uninstall_cmd,
        **{
            "global": True,
            "platforms": platforms,
        },
    )


@cli.command("update", short_help="Update installed development platforms")
@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]")
@click.option(
    "-p", "--only-packages", is_flag=True, help="Update only the platform packages"
)
@click.option(
    "-c",
    "--only-check",
    is_flag=True,
    help="DEPRECATED. Please use `--dry-run` instead",
)
@click.option(
    "--dry-run", is_flag=True, help="Do not update, only check for the new versions"
)
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
@click.option("--json-output", is_flag=True)
@click.pass_context
def platform_update(  # pylint: disable=too-many-locals, too-many-arguments
    ctx, platforms, only_check, dry_run, silent, json_output, **_
):
    only_check = dry_run or only_check

    if only_check and not json_output:
        raise UserSideException(
            "This command is deprecated, please use `pio pkg outdated` instead"
        )

    if not json_output:
        click.secho(
            "\nWARNING: This command is deprecated and will be removed in "
            "the next releases. \nPlease use `pio pkg update` instead.\n",
            fg="yellow",
        )
        return ctx.invoke(
            package_update_cmd,
            **{
                "global": True,
                "platforms": platforms,
                "silent": silent,
            },
        )

    pm = PlatformPackageManager()
    pm.set_log_level(logging.WARN if silent else logging.DEBUG)
    platforms = platforms or pm.get_installed()
    result = []
    for platform in platforms:
        spec = None
        pkg = None
        if isinstance(platform, PackageItem):
            pkg = platform
        else:
            spec = PackageSpec(platform)
            pkg = pm.get_package(spec)
        if not pkg:
            continue
        outdated = pm.outdated(pkg, spec)
        if (
            not outdated.is_outdated(allow_incompatible=True)
            and not PlatformFactory.new(pkg).are_outdated_packages()
        ):
            continue
        data = _get_installed_platform_data(
            pkg, with_boards=False, expose_packages=False
        )
        if outdated.is_outdated(allow_incompatible=True):
            data["versionLatest"] = str(outdated.latest) if outdated.latest else None
        result.append(data)
    click.echo(json.dumps(result))
    return True


#
# Helpers
#


def _get_registry_platforms():
    regclient = PlatformPackageManager().get_registry_client_instance()
    return regclient.fetch_json_data("get", "/v2/platforms", x_cache_valid="1d")


def _get_platform_data(*args, **kwargs):
    try:
        return _get_installed_platform_data(*args, **kwargs)
    except UnknownPlatform:
        return _get_registry_platform_data(*args, **kwargs)


def _get_installed_platform_data(platform, with_boards=True, expose_packages=True):
    p = PlatformFactory.new(platform)
    data = dict(
        name=p.name,
        title=p.title,
        description=p.description,
        version=p.version,
        homepage=p.homepage,
        url=p.homepage,
        repository=p.repository_url,
        license=p.license,
        forDesktop=not p.is_embedded(),
        frameworks=sorted(list(p.frameworks) if p.frameworks else []),
        packages=list(p.packages) if p.packages else [],
    )

    # if dump to API
    # del data['version']
    # return data

    # overwrite VCS version and add extra fields
    manifest = PlatformPackageManager().legacy_load_manifest(
        os.path.dirname(p.manifest_path)
    )
    assert manifest
    for key in manifest:
        if key == "version" or key.startswith("__"):
            data[key] = manifest[key]

    if with_boards:
        data["boards"] = [c.get_brief_data() for c in p.get_boards().values()]

    if not data["packages"] or not expose_packages:
        return data

    data["packages"] = []
    installed_pkgs = {
        pkg.metadata.name: p.pm.load_manifest(pkg) for pkg in p.get_installed_packages()
    }
    for name, options in p.packages.items():
        item = dict(
            name=name,
            type=p.get_package_type(name),
            requirements=options.get("version"),
            optional=options.get("optional") is True,
        )
        if name in installed_pkgs:
            for key, value in installed_pkgs[name].items():
                if key not in ("url", "version", "description"):
                    continue
                item[key] = value
                if key == "version":
                    item["originalVersion"] = get_original_version(value)
        data["packages"].append(item)

    return data


def _get_registry_platform_data(  # pylint: disable=unused-argument
    platform, with_boards=True, expose_packages=True
):
    _data = None
    for p in _get_registry_platforms():
        if p["name"] == platform:
            _data = p
            break

    if not _data:
        return None

    data = dict(
        ownername=_data.get("ownername"),
        name=_data["name"],
        title=_data["title"],
        description=_data["description"],
        homepage=_data["homepage"],
        repository=_data["repository"],
        url=_data["url"],
        license=_data["license"],
        forDesktop=_data["forDesktop"],
        frameworks=_data["frameworks"],
        packages=_data["packages"],
        versions=_data.get("versions"),
    )

    if with_boards:
        data["boards"] = [
            board
            for board in PlatformPackageManager().get_registered_boards()
            if board["platform"] == _data["name"]
        ]

    return data
