#!python
import io
import os
from pathlib import Path
from typing import Optional, List
from zipfile import ZipFile
import emoji

import typer
from typing_extensions import Annotated
from dm_cli.command_group.data_source import data_source_app, reset_data_source
from dm_cli.command_group.entities import entities_app, import_entity
from dm_cli.state import state
from dm_cli.dmss import export, dmss_api, dmss_exception_wrapper
from dm_cli.utils.zip import unpack_and_save_zipfile, save_as_zip_file
from dm_cli import VERSION
from dm_cli.utils.utils import get_root_packages_in_data_sources, validate_entities_in_data_sources
from dm_cli.utils.file_structure import get_app_dir_structure, get_json_files_in_dir

app = typer.Typer(pretty_exceptions_short=True)
app.add_typer(data_source_app, name="ds")
app.add_typer(entities_app, name="entities")


def version_callback(print_version: bool):
    if print_version:
        print(VERSION)
        raise typer.Exit()

@app.callback()
def main(
        force: bool = typer.Option(False, "--force", "-f", help="Force the operation. Overwriting and potentially deleting data."),
        dmss_url: str = typer.Option("http://localhost:5000", "--url", "-u", help="URL to the Data Modelling Storage Service (DMSS)."),
        token: str = typer.Option("no-token", "--token", "-t", help="Token for authentication against DMSS."),
        debug: bool = typer.Option(False, "--debug", "-d", help="Print stack trace of suppressed exceptions"),
        version: Optional[bool] = typer.Option(None, "--version", "-v", callback=version_callback, is_eager=True, help="Print version and exit")
):
    """
    Command Line Interface (CLI) tool for working with the Data Modelling Framework.
    This tool is mainly used to upload data source definitions, models and entities, and creating RecipeLink-tables.
    """
    state.force = force
    state.dmss_url = dmss_url
    state.token = token
    state.debug = debug

    dmss_api.api_client.default_headers["Authorization"] = f"Bearer {token}"
    dmss_api.api_client.configuration.host = dmss_url

@app.command("import-plugin-blueprints")
def import_plugin_blueprints(
        path: Annotated[str, typer.Argument(help="Path to file or folder on local filesystem to import. Trailing '/' will result in the content being imported instead of the folder itself.")],
        validate: Annotated[bool, typer.Option(help="if True, all entities uploaded will be validated.")] = True
):
    """
    Import blueprints from a plugin into the standard location 'system/Plugins/<plugin-name>'.
    """
    state.force = True
    dmss_exception_wrapper(import_entity, source=f"{path}/blueprints/", destination=f"system/Plugins/{Path(path).name}", validate=validate)

@app.command("create-lookup")
def create_lookup(name: Annotated[str, typer.Argument(help="Name of the lookup (application context) to create.")],
                  paths: Annotated[List[Path], typer.Argument(help="one or more remote location for recursively looking for RecipeLinks. "
                                                                  "A remote location is a path in DMSS on the format <dataSource>/<rootPackage>/<subPacakge>/...")]):
    """
    Create a named Ui-/StorageRecipe-lookup-table from all RecipeLinks in a package existing in DMSS (requires admin privileges).
    """
    # TODO change type of paths argument to be list of str to avoid this path as strings conversion below
    paths_as_strings = [str(path) for path in paths]
    print(f"Creating lookup table from paths: {paths_as_strings}")
    dmss_exception_wrapper(dmss_api.create_lookup, recipe_package=paths_as_strings, application=name)

@app.command("export")
def export_entity(target: Annotated[str, typer.Argument(help="Address to the entity to export. Format: <dataSource>/<path>.")],
                  export_location: Annotated[str, typer.Argument(help="Path on local filesystem to store the exported document(s) to. If not provided, will export to current folder.")] = "",
                  unpack: Annotated[bool, typer.Option(help="Whether or not to unpack the zip file containing the exported entities(s).")] = False):
    """
    Export one or more entities.

    Example: if you have a data_source "DemoApplicationDataSource" with the root package "models" and a sub package "Windmill",
    the entire "Windmill" package can be exported using: target = "DemoApplicationDataSource/models/Windmill".
    The 'target' parameter can either point to a single entity or to a package.

    If the file or folder to export already exists on local disk, an exception is raised.
    """
    response = dmss_exception_wrapper(export, target)

    if not export_location:
        export_location = os.getcwd()
    with ZipFile(io.BytesIO(response.content), "r") as zip_file:
        zip_file.filename = f"{target.split('/')[-1]}.zip"
        if unpack:
            unpack_and_save_zipfile(export_location=export_location, zip_file=zip_file)
        else:
            save_as_zip_file(export_location=export_location, filename=zip_file.filename, data=response.content)



@app.command("reset")
def reset(path: Annotated[str, typer.Argument(help="The path on local file system to the data source you want to reset.")],
          validate_entities: Annotated[bool, typer.Option(help="if True, all entities uploaded to DMSS will be validated.")] = True,
          resolve_local_ids: Annotated[bool, typer.Option(help="if True, will resolve all local ids found in all entities")] = False):
    """
    Reset all data sources (deletes and re-uploads all packages to DMSS).
    """
    # Check for presence of expected directories, 'data_sources' and 'data'
    data_sources_dir, data_dir = get_app_dir_structure(Path(path))

    data_source_definitions = get_json_files_in_dir(data_sources_dir)
    if not data_source_definitions:
        print(emoji.emojize(f"\t:warning: No data source definitions were found in '{data_sources_dir}'."))

    for data_source_definition_filename in data_source_definitions:
        data_source_name = data_source_definition_filename.replace(".json", "")

        data_source_data_dir = data_dir.joinpath(data_source_name)
        if not data_source_data_dir.is_dir():
            print(
                emoji.emojize(
                    f"\t:warning: No data source data directory was found by the name '{data_source_name}' in '{data_dir}'."
                )
            )
            continue

        dmss_exception_wrapper(reset_data_source, data_source=data_source_name, path=path, resolve_local_ids=resolve_local_ids)
    data_source_contents = get_root_packages_in_data_sources(path)
    if (validate_entities):
        dmss_exception_wrapper(validate_entities_in_data_sources, data_source_contents)

if __name__ == "__main__":
    app()
