#!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, Package, Template, Validate


app = typer.Typer()


@app.command()
def deps(
    branch: Optional[str] = typer.Option(
        'master',
        help=(
            'The git branch of the tcex repository to use. '
            'This override what is in the requirements.txt file.',
        ),
    ),
    no_cache_dir: Optional[bool] = typer.Option(False, help='Do not use pip cache directory.'),
    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:
        tcl = Dep(branch, no_cache_dir, proxy_host, proxy_port, proxy_user, proxy_pass)

        # configure proxy settings
        tcl.configure_proxy()

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

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


@app.command()
def deploy(
    server: str = typer.Argument(..., envvar='TC_DEPLOY_SERVER'),
    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, app_file, proxy_host, proxy_port, proxy_user, proxy_pass)

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


@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.'
    ),
):
    """Initialize a new App from a template.

    Templates can be found at: https://github.com/ThreatConnect-Inc/tcex-app-templates
    """
    tt = Template()
    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:
        typer.echo(f'Failed initializing App ({ex}).', err=True)
        # raise typer.Exit(code=1)
        raise


@app.command()
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.'
    ),
):
    """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()
    try:
        tt.list(branch, type_)
        tt.print_list(branch)
        tt.print_error_message()
    except Exception as ex:
        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.
        typer.echo(f'{c.Style.BRIGHT}{c.Fore.RED}{traceback.format_exc()}', err=True)
        raise typer.Exit(code=tcv.exit_code)


# TODO --force
@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."
    ),
):
    """Update an project with 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()
    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:
        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 the install.json is valid syntax
    * validate the layout.json is 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:
        typer.echo(f'Validate Exception: {ex}', err=True)
        raise typer.Exit(code=1)


if __name__ == '__main__':
    app()
