# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

import logging
import zipfile
from zipfile import ZipFile
from dataclasses import dataclass
from typing import Set, List

from pathlib import Path
from jinja2 import Environment, FileSystemLoader

from .. import run_command


@dataclass
class AndroidData:
    """
    Dataclass to store all the Android data obtained through cli
    """
    wheel_pyside: Path
    wheel_shiboken: Path
    ndk_path: Path
    sdk_path: Path


def create_recipe(version: str, component: str, wheel_path: str, generated_files_path: Path,
                  qt_modules: List[str] = None, local_libs: List[str] = None,
                  plugins: List[str] = None):
    '''
    Create python_for_android recipe for PySide6 and shiboken6
    '''
    qt_plugins = []
    if plugins:
        # split plugins based on category
        for plugin in plugins:
            plugin_category, plugin_name = plugin.split('_', 1)
            qt_plugins.append((plugin_category, plugin_name))

    qt_local_libs = []
    if local_libs:
        qt_local_libs = [local_lib for local_lib in local_libs if local_lib.startswith("Qt6") ]

    rcp_tmpl_path = Path(__file__).parent / "recipes" / f"{component}"
    environment = Environment(loader=FileSystemLoader(rcp_tmpl_path))
    template = environment.get_template("__init__.tmpl.py")
    content = template.render(
        version=version,
        wheel_path=wheel_path,
        qt_modules=qt_modules,
        qt_local_libs=qt_local_libs,
        qt_plugins=qt_plugins
    )

    recipe_path = generated_files_path / "recipes" / f"{component}"
    recipe_path.mkdir(parents=True, exist_ok=True)
    logging.info(f"[DEPLOY] Writing {component} recipe into {str(recipe_path)}")
    with open(recipe_path / "__init__.py", mode="w", encoding="utf-8") as recipe:
        recipe.write(content)


def extract_and_copy_jar(wheel_path: Path, generated_files_path: Path) -> str:
    '''
    extracts the PySide6 wheel and copies the 'jar' folder to 'generated_files_path'.
    These .jar files are added to the buildozer.spec file to be used later by buildozer
    '''
    jar_path = generated_files_path / "jar"
    jar_path.mkdir(parents=True, exist_ok=True)
    archive = ZipFile(wheel_path)
    jar_files = [file for file in archive.namelist() if file.startswith("PySide6/jar")]
    for file in jar_files:
        archive.extract(file, jar_path)
    return (jar_path / "PySide6" / "jar").resolve() if jar_files else None


def get_wheel_android_arch(wheel: Path):
    '''
    Get android architecture from wheel
    '''
    supported_archs = ["aarch64", "armv7a", "i686", "x86_64"]
    for arch in supported_archs:
        if arch in wheel.stem:
            return arch

    return None


def get_llvm_readobj(ndk_path: Path) -> Path:
    '''
    Return the path to llvm_readobj from the Android Ndk
    '''
    # TODO: Requires change if Windows platform supports Android Deployment or if we
    # support host other than linux-x86_64
    return (ndk_path / "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readobj")


def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies: Set[str] = None,
                          dry_run: bool = False):
    """
    Find all the Qt dependencies of a library using llvm_readobj
    """
    if lib_path.name in used_dependencies:
        return

    command = [str(llvm_readobj), "--needed-libs", str(lib_path)]
    _, output = run_command(command=command, dry_run=dry_run, fetch_output=True)

    dependencies = set()
    neededlibraries_found = False
    for line in output.splitlines():
        line = line.decode("utf-8").lstrip()
        if line.startswith("NeededLibraries") and not neededlibraries_found:
            neededlibraries_found = True
        if neededlibraries_found and line.startswith("libQt"):
            dependencies.add(line)
            used_dependencies.add(line)
            dependent_lib_path = lib_path.parent / line
            find_lib_dependencies(llvm_readobj, dependent_lib_path, used_dependencies, dry_run)

    if dependencies:
        logging.info(f"[DEPLOY] Following dependencies found for {lib_path.stem}: {dependencies}")
    else:
        logging.info(f"[DEPLOY] No Qt dependencies found for {lib_path.stem}")


def find_qtlibs_in_wheel(wheel_pyside: Path):
    """
    Find the path to Qt/lib folder inside the wheel.
    """
    archive = ZipFile(wheel_pyside)
    qt_libs_path = wheel_pyside / "PySide6/Qt/lib"
    qt_libs_path = zipfile.Path(archive, at=qt_libs_path)
    if not qt_libs_path.exists():
        for file in archive.namelist():
            # the dependency files are inside the libs folder
            if file.endswith("android-dependencies.xml"):
                qt_libs_path = zipfile.Path(archive, at=file).parent
                # all dependency files are in the same path
                break

    if not qt_libs_path:
        raise FileNotFoundError("[DEPLOY] Unable to find Qt libs folder inside the wheel")

    return qt_libs_path
