#!/usr/bin/env python
"""TcEx framework CLI command"""
# standard library
import os
import traceback
from pathlib import Path
from typing import Optional

# third-party
import colorama as c
import typer

# first-party
from tcex.bin import Dep, Deploy, SpecTool, Package, Template, Validate


# initialize typer
app = typer.Typer()


@app.command()
def deps(
    branch: Optional[str] = typer.Option(
        'main',
        help=(
            'The git branch of the tcex repository to use. '
            'This override what is in the requirements.txt file.'
        ),
    ),
    dev: Optional[bool] = typer.Option(False, help='Install development dependencies.'),
    no_cache_dir: Optional[bool] = typer.Option(False, help='Do not use pip cache directory.'),
    pre: Optional[bool] = typer.Option(False, help='Install pre-release packages.'),
    proxy_host: Optional[str] = typer.Option(
        None, help='(Advanced) Hostname for the proxy server.'
    ),
    proxy_port: Optional[int] = typer.Option(
        None, help='(Advanced) Port number for the proxy server.'
    ),
    proxy_user: Optional[str] = typer.Option(
        None, help='(Advanced) Username for the proxy server.'
    ),
    proxy_pass: Optional[str] = typer.Option(
        None, help='(Advanced) Password for the proxy server.'
    ),
):
    """Install dependencies defined in the requirements.txt file.


    If no Python version is defined in the tcex.json file, the version of Python calling this
    command will be used to create the lib_X.X.X directory. If multiple versions of Python
    are defined in the tcex.json file, then there will be multiple lib_X.X.X directories created.
    """
    try:
        tcd = Dep(branch, dev, no_cache_dir, pre, proxy_host, proxy_port, proxy_user, proxy_pass)

        # configure proxy settings
        tcd.configure_proxy()

        if branch != 'main':
            # create temp requirements.txt file pointing to tcex branch
            tcd.create_temp_requirements()

        # install debs
        tcd.install_deps()
    except Exception as ex:
        tcd.log.error(traceback.format_exc())
        typer.echo(f'Exception: {ex}', err=True)
        raise typer.Exit(code=1)


@app.command()
def deploy(
    server: str = typer.Argument(..., envvar='TC_DEPLOY_SERVER'),
    allow_all_orgs: bool = typer.Option(True, help='If true all orgs are able to use the App.'),
    allow_distribution: bool = typer.Option(
        True, help='If true the App is allowed to be distributed.'
    ),
    app_file: Optional[str] = typer.Option(
        None,
        help='The fully qualified path to App file. Will be auto-detected if not provided.',
    ),
    proxy_host: Optional[str] = typer.Option(
        None, help='(Advanced) Hostname for the proxy server.'
    ),
    proxy_port: Optional[int] = typer.Option(
        None, help='(Advanced) Port number for the proxy server.'
    ),
    proxy_user: Optional[str] = typer.Option(
        None, help='(Advanced) Username for the proxy server.'
    ),
    proxy_pass: Optional[str] = typer.Option(
        None, help='(Advanced) Password for the proxy server.'
    ),
):
    """Deploy App to ThreatConnect via internal API.

    WARNING: This command is not intended for production use and requires special configuration
    in the ThreatConnect platform to work.

    This command REQUIRES the following environment variables to be set.

    * TC_API_PATH
    * TC_API_ACCESS_ID
    * TC_API_SECRET_KEY
    """
    try:
        tcd = Deploy(
            server,
            allow_all_orgs,
            allow_distribution,
            app_file,
            proxy_host,
            proxy_port,
            proxy_user,
            proxy_pass,
        )

        # deploy the App
        tcd.deploy_app()
    except Exception as ex:
        tcd.log.error(traceback.format_exc())
        typer.echo(f'Exception: {ex}', err=True)
        raise typer.Exit(code=1)


@app.command()
def spectool(
    all_: bool = typer.Option(False, '--all', help='Generate all configuration files.'),
    app_input: bool = typer.Option(False, help='Generate app_input.py.'),
    app_spec: bool = typer.Option(False, help='Generate app_spec.yml.'),
    install_json: bool = typer.Option(False, help='Generate install.json.'),
    job_json: bool = typer.Option(False, help='Generate job.json.'),
    layout_json: bool = typer.Option(False, help='Generate layout.json.'),
    readme_md: bool = typer.Option(False, help='Generate README.md.'),
    overwrite: bool = typer.Option(False, help='Force overwrite of config file.'),
    schema: bool = typer.Option(False, help='When set output schema instead of file.'),
    tcex_json: bool = typer.Option(False, help='Generate tcex.json.'),
):
    """Generate App configuration file.

    Generate one or more configuration files for the App.
    """
    gc = SpecTool(overwrite)
    try:
        if app_spec is True:
            gc.generate_app_spec(schema)
        else:
            if install_json is True or all_ is True:
                gc.generate_install_json(schema)

            if layout_json is True or all_ is True:
                gc.generate_layout_json(schema)

            if job_json is True or all_ is True:
                gc.generate_job_json(schema)

            if tcex_json is True or all_ is True:
                gc.generate_tcex_json(schema)

            if app_input is True or all_ is True:
                gc.generate_app_input()

            if readme_md is True or all_ is True:
                gc.generate_readme_md()

        gc.print_summary()
        gc.print_report()
    except Exception as ex:
        gc.log.error(traceback.format_exc())
        typer.echo(f'Failed generating code/config: {ex}', err=True)
        raise typer.Exit(code=1)


@app.command()
def init(
    type_: str = typer.Option(..., '--type', help='The App type being initialized.', prompt=True),
    template: str = typer.Option(..., help='The App template to be used.', prompt=True),
    force: bool = typer.Option(False, help='Force App init even if files exist in directory.'),
    branch: Optional[str] = typer.Option(
        'main', help='The git branch of the tcex-app-template repository to use.'
    ),
    app_builder: Optional[bool] = typer.Option(
        False, help='Include .appbuilderconfig file in template download.'
    ),
    proxy_host: Optional[str] = typer.Option(
        None, help='(Advanced) Hostname for the proxy server.'
    ),
    proxy_port: Optional[int] = typer.Option(
        None, help='(Advanced) Port number for the proxy server.'
    ),
    proxy_user: Optional[str] = typer.Option(
        None, help='(Advanced) Username for the proxy server.'
    ),
    proxy_pass: Optional[str] = typer.Option(
        None, help='(Advanced) Password for the proxy server.'
    ),
):
    """Initialize a new App from a template.

    Templates can be found at: https://github.com/ThreatConnect-Inc/tcex-app-templates
    """
    tt = Template(
        proxy_host,
        proxy_port,
        proxy_user,
        proxy_pass,
    )
    if os.listdir(os.getcwd()) and force is False:
        tt.print_block(
            'The current directory does not appear to be empty. Apps should '
            'be initialized in an empty directory. If attempting to update an '
            'existing App then please try using the "tcex update" command instead.',
            fg_color='yellow',
        )
        raise typer.Exit(code=1)

    try:
        tt.print_title('Installing template files ...', divider=False, fg_color='white')
        downloads = tt.init(branch, type_, template, app_builder)
        typer.echo('')  # add additional space before progress bar
        with typer.progressbar(downloads, label='Downloading', width=80) as progress:
            for item in progress:
                tt.download_template_file(item)

        # update tcex.json file with template data, external App do not support tcex.json
        if type_ != 'external':
            tt.update_tcex_json()

            # update manifest
            tt.template_manifest_write()

        typer.echo('\n')
        tt.print_title('Installation Summary')
        tt.print_setting('Template Type', type_)
        tt.print_setting('Template Name', template)
        tt.print_setting('Files Downloaded', len(downloads))
        tt.print_setting('Branch', branch)
    except Exception as ex:
        tt.log.error(traceback.format_exc())
        typer.echo(f'Failed initializing App ({ex}).', err=True)
        raise typer.Exit(code=1)


@app.command('list')
def _list(
    type_: Optional[str] = typer.Option(None, '--type', help='The App type being initialized.'),
    branch: Optional[str] = typer.Option(
        'main', help='The git branch of the tcex-app-template repository to use.'
    ),
    proxy_host: Optional[str] = typer.Option(
        None, help='(Advanced) Hostname for the proxy server.'
    ),
    proxy_port: Optional[int] = typer.Option(
        None, help='(Advanced) Port number for the proxy server.'
    ),
    proxy_user: Optional[str] = typer.Option(
        None, help='(Advanced) Username for the proxy server.'
    ),
    proxy_pass: Optional[str] = typer.Option(
        None, help='(Advanced) Password for the proxy server.'
    ),
):
    """List templates

    The template name will be pulled from tcex.json by default. If the template option
    is provided it will be used instead of the value in the tcex.json file. The tcex.json
    file will also be updated with new values.
    """
    tt = Template(
        proxy_host,
        proxy_port,
        proxy_user,
        proxy_pass,
    )
    try:
        tt.list(branch, type_)
        tt.print_list(branch)
        tt.print_error_message()
    except Exception as ex:
        tt.log.error(traceback.format_exc())
        typer.echo(f'Failed getting App templates: {ex}', err=True)
        raise typer.Exit(code=1)


@app.command()
def package(
    excludes: Optional[str] = typer.Option(
        None, help='File and directories to exclude from build in a comma-seperated list.'
    ),
    ignore_validation: Optional[bool] = typer.Option(
        False, help='If true, validation errors will not prevent package.'
    ),
    json_output: Optional[bool] = typer.Option(
        False, help='If true, the output of the validation will be returned in JSON format.'
    ),
    output_dir: Path = typer.Option(
        'target', help='(Advanced) Directory name (relative to App) to write the App package.'
    ),
):
    """Package the current App.

    This command will write an <app name>.tcx file to the output_dir (default: target). This
    App package can be directly installed into ThreatConnect.
    """
    if excludes:
        excludes = [e.strip() for e in excludes.split(',')]

    try:
        # validate App
        tcv = Validate(ignore_validation)
        tcv.update_system_path()
        tcv.check_syntax()
        tcv.check_imports()
        tcv.check_install_json()
        tcv.check_layout_json()
        tcv.check_job_json()
        if not json_output:
            tcv.print_results()
        if tcv.exit_code != 0:
            raise typer.Exit(code=tcv.exit_code)

        # package App
        tcp = Package(excludes, ignore_validation, output_dir)
        tcp.validation_data = tcv.validation_data
        tcp.package()
        if json_output:
            tcp.print_json()
        else:
            tcp.print_results()
    except Exception:
        # TODO: [med] update this to a more user friendly error message.
        tcv.log.error(traceback.format_exc())
        typer.echo(f'{c.Style.BRIGHT}{c.Fore.RED}{traceback.format_exc()}', err=True)
        raise typer.Exit(code=tcv.exit_code)


@app.command()
def update(
    template: Optional[str] = typer.Option(
        None, help='Only provide this value if changing the saved value.'
    ),
    type_: str = typer.Option(None, '--type', help='The App type being initialized.'),
    branch: Optional[str] = typer.Option(
        'main', help='The git branch of the tcex-app-template repository to use.'
    ),
    force: bool = typer.Option(
        False, help="Update files from template even if they haven't changed."
    ),
    proxy_host: Optional[str] = typer.Option(
        None, help='(Advanced) Hostname for the proxy server.'
    ),
    proxy_port: Optional[int] = typer.Option(
        None, help='(Advanced) Port number for the proxy server.'
    ),
    proxy_user: Optional[str] = typer.Option(
        None, help='(Advanced) Username for the proxy server.'
    ),
    proxy_pass: Optional[str] = typer.Option(
        None, help='(Advanced) Password for the proxy server.'
    ),
):
    """Update a project with the latest template files.

    Templates can be found at: https://github.com/ThreatConnect-Inc/tcex-app-templates

    The template name will be pulled from tcex.json by default. If the template option
    is provided it will be used instead of the value in the tcex.json file. The tcex.json
    file will also be updated with new values.
    """
    # external Apps do not support update
    if not os.path.isfile('tcex.json'):
        typer.secho(
            'Update requires a tcex.json file, "external" App templates can not be update.',
            fg=typer.colors.RED,
        )
        raise typer.Exit(code=1)

    tt = Template(
        proxy_host,
        proxy_port,
        proxy_user,
        proxy_pass,
    )
    try:
        downloads = tt.update(branch, template, type_, force)
        tt.print_title('Updating template files ...', divider=False, fg_color='white')
        if not downloads:
            typer.secho('\nNo files to update.', fg=typer.colors.GREEN)
        else:
            typer.echo('')  # add additional space before progress bar
            with typer.progressbar(downloads, label='Downloading', length=80) as progress:
                for item in progress:
                    tt.download_template_file(item)

        # update manifest
        tt.template_manifest_write()

        # update tcex.json file
        tt.update_tcex_json()

        typer.echo('\n')
        tt.print_title('Update Summary')
        tt.print_setting('Template Type', tt.tj.model.template_type)
        tt.print_setting('Template Name', tt.tj.model.template_name)
        tt.print_setting('Files Updated', len(downloads))
        tt.print_setting('Branch', branch)
    except Exception as ex:
        tt.log.error(traceback.format_exc())
        typer.echo(f'Failed updating App ({ex}).', err=True)
        raise typer.Exit(code=1)


@app.command()
def validate(
    ignore_validation: Optional[bool] = typer.Option(
        False, help='If true, validation errors will not cause an exit.'
    ),
    interactive: Optional[bool] = typer.Option(
        False, help='(Advanced) If true, this command will not exit until passed an exit string.'
    ),
):
    """Run validation of the current App.

    Validations:
    * validate Python files have valid syntax
    * validate all imports are available in lib_ directories
    * validate install.json has valid syntax
    * validate layout.json has valid syntax
    * validate the feed files are valid
    """
    try:
        tcv = Validate(ignore_validation)
        tcv.update_system_path()
        # run in interactive
        if interactive:
            print('type', type(tcv))
            tcv.interactive()
        else:
            tcv.check_syntax()
            tcv.check_imports()
            tcv.check_install_json()
            tcv.check_layout_json()
            tcv.check_job_json()
            tcv.print_results()
    except Exception as ex:
        tcv.log.error(traceback.format_exc())
        typer.echo(f'Validate Exception: {ex}', err=True)
        raise typer.Exit(code=1)


if __name__ == '__main__':
    app()
