import difflib
import os.path
import sys
from pathlib import Path
from tempfile import NamedTemporaryFile

import click

from .engine import SedgeEngine, ConfigOutput
from .keylib import KeyLibrary
from .templates import sedge_config_header


def get_file_backup_name(file_name):
    return file_name + ".pre_sedge"


def ask_overwrite(file_name):
    click.echo(
        "WARNING: '{}' already exists and was not generated by sedge.".format(
            file_name
        ),
        err=True,
    )
    confirm = input(
        "Enter 'yes' to overwrite (I'll make a backup called '{backup_file}'): ".format(
            backup_file=get_file_backup_name(file_name)
        )
    )
    return confirm == "yes"


def backup_file(file_name):
    _backup_file = get_file_backup_name(file_name)
    os.rename(file_name, _backup_file)
    click.echo(
        "Your previous SSH configuration file has been renamed to: {}".format(
            _backup_file
        ),
        err=True,
    )


def check_or_confirm_output_overwrite(file_name):
    """
    Returns True if OK to proceed, False otherwise
    """

    def prompt():
        okay = ask_overwrite(file_name)
        if okay:
            backup_file(file_name)
            return True
        else:
            return False

    try:
        with open(file_name) as fd:
            header = next(fd)
            if header.find(":sedge:") == -1:
                return prompt()
            return True
    except FileNotFoundError:
        # this is fine: if we simply don't have an existing
        # configuration, we can proceed to write the new open
        # over the top
        return True
    except StopIteration:
        # probably an empty existing SSH config: prompt
        return prompt()


def diff_config_changes(before, after):
    def get_data(f):
        try:
            with open(f) as fd:
                return fd.read().splitlines(True)
        except FileNotFoundError:
            click.echo("{} not found".format(f), err=True)
            return []

    a = get_data(before)
    b = get_data(after)
    diff_lines = list(difflib.unified_diff(a, b))
    if not diff_lines:
        click.echo("no changes.", err=True)
    else:
        click.echo("configuration changes:", err=True)
        click.echo("".join(diff_lines), err=True)


class SedgeConfig:
    pass


sedge_config = click.make_pass_decorator(SedgeConfig, ensure=True)


@click.group()
@click.version_option()
@click.option(
    "-c", "--config-file", default=os.path.expanduser("~/.sedge/config"),
)
@click.option(
    "-o", "--output-file", default=os.path.expanduser("~/.ssh/config"),
)
@click.option("-n", "--no-verify", is_flag=True, help="do not verify HTTPS requests")
@click.option(
    "-k",
    "--key-directory",
    default=os.path.expanduser("~/.ssh"),
    help="directory to scan for SSH keys",
)
@click.option("-v", "--verbose", count=True, default=0)
@sedge_config
def cli(config, verbose, key_directory, no_verify, output_file, config_file):
    """
    Template and share OpenSSH ssh_config(5) files. A preprocessor for
    OpenSSH configurations.
    """
    config.verbose = verbose
    config.key_directory = key_directory
    config.config_file = config_file
    config.output_file = output_file
    config.no_verify = no_verify


@cli.command("init")
@sedge_config
def init(config):
    """
    Initialise ~./sedge/config file if none exists.
    Good for first time sedge usage
    """
    from pkg_resources import resource_stream
    import shutil

    config_file = Path(config.config_file)
    if config_file.is_file():
        click.echo(
            "{} already exists, maybe you want $ sedge update".format(config_file)
        )
        sys.exit()

    config_file.parent.mkdir(parents=True, exist_ok=True)
    with resource_stream(__name__, "sedge_template.conf") as src_stream:
        with open(config.config_file, "wb") as target_stream:
            shutil.copyfileobj(src_stream, target_stream)


@cli.command("update")
@sedge_config
def update(config):
    """
    Update ssh config from sedge specification
    """

    def write_to(out):
        engine.output(out)

    config_file = Path(config.config_file)
    if not config_file.is_file():
        click.echo("No file {} ".format(config_file), err=True)
        sys.exit()

    library = KeyLibrary(config.key_directory)
    with config_file.open() as fd:
        engine = SedgeEngine(library, fd, not config.no_verify, url=config.config_file)

    if config.output_file == "-":
        write_to(ConfigOutput(sys.stdout))
        return

    # ensure that there is a directory for output
    config_dir = os.path.dirname(config.output_file)
    try:
        os.mkdir(config_dir)
    except FileExistsError:
        pass

    if not check_or_confirm_output_overwrite(config.output_file):
        click.echo("Aborting.", err=True)
        sys.exit(1)

    tmp_file = NamedTemporaryFile(
        mode="w", dir=os.path.dirname(config.output_file), delete=False
    )
    try:
        tmp_file.file.write(sedge_config_header.format(config.config_file))
        write_to(ConfigOutput(tmp_file.file))
        tmp_file.close()
        if config.verbose:
            diff_config_changes(config.output_file, tmp_file.name)
        os.rename(tmp_file.name, config.output_file)
    except Exception:
        os.unlink(tmp_file.name)
        raise


@cli.group()
@sedge_config
def keys(config):
    """
    Manage ssh keys
    """


@keys.command("list")
@sedge_config
def command_list_keys(config):
    library = KeyLibrary(path=config.key_directory, verbose=config.verbose)
    library.list_keys()


@keys.command("add")
@sedge_config
def command_add_keys(config):
    library = KeyLibrary(path=config.key_directory, verbose=config.verbose)
    library.add_keys()
