import json
from argparse import ArgumentParser, ArgumentTypeError, Namespace
from os.path import exists
from tempfile import NamedTemporaryFile
from typing import Optional, Sequence, Text

from luh3417.luhfs import parse_location
from luh3417.luhphp import parse_wp_config
from luh3417.restore.__main__ import main as restore
from luh3417.snapshot.__main__ import main as snapshot
from luh3417.transfer import UnknownEnvironment, apply_wp_config
from luh3417.utils import import_file, make_doer, run_main, setup_logging

doing = make_doer("luh3417.transfer")


def generator_validator(file_path: Text):
    """
    Validates that the generator module exists and has all the required
    methods
    """

    if not exists(file_path):
        raise ArgumentTypeError(f"File {file_path} could not be found")

    try:
        module = import_file("generator", file_path)
    except SyntaxError:
        doing.logger.exception("Syntax error in generator")
        raise ArgumentTypeError(f"File {file_path} has a syntax error")
    except ImportError:
        raise ArgumentTypeError(f"File {file_path} cannot be imported")
    except Exception:
        doing.logger.exception("Unknown error while importing generator")
        raise ArgumentTypeError(f"Unknown error")

    if not hasattr(module, "get_source") or not callable(module.get_source):
        raise ArgumentTypeError(
            f"Generator does not expose a get_source(environment_name) method"
        )

    if not hasattr(module, "allow_transfer") or not callable(module.allow_transfer):
        raise ArgumentTypeError(
            f"Generator does not expose a allow_transfer(origin, target) method"
        )

    if not hasattr(module, "get_backup_dir") or not callable(module.get_backup_dir):
        raise ArgumentTypeError(
            f"Generator does not expose a get_backup_dir(environment) method"
        )

    if not hasattr(module, "get_patch") or not callable(module.get_backup_dir):
        raise ArgumentTypeError(
            f"Generator does not expose a get_patch(origin, target) method"
        )

    if not hasattr(module, "get_wp_config") or not callable(module.get_backup_dir):
        raise ArgumentTypeError(
            f"Generator does not expose a get_wp_config(environment) method"
        )

    return module


def parse_args(args: Optional[Sequence[str]] = None) -> Namespace:
    """
    Parses args and validates the consistency of origin/target using the
    generator
    """

    parser = ArgumentParser(
        prog="python -m luh3417.transfer",
        description="Transfers a WordPress to one location to the other",
    )

    parser.add_argument(
        "-g",
        "--settings-generator",
        help="A Python script that handles the transitions",
        type=generator_validator,
        required=True,
    )

    parser.add_argument("origin", help="Origin environment")
    parser.add_argument("target", help="Target environment")

    parsed = parser.parse_args(args)

    for env in ["origin", "target"]:
        env_name = getattr(parsed, env)

        try:
            parsed.settings_generator.get_source(env_name)
        except UnknownEnvironment as e:
            parser.error(
                f'Environment "{env_name}" not recognized by generator: {e.message}'
            )

    if not parsed.settings_generator.allow_transfer(parsed.origin, parsed.target):
        parser.error(
            f"Generator does not allow transfer from {parsed.origin} to {parsed.target}"
        )

    return parsed


def main(args: Optional[Sequence[str]] = None):
    """
    Executes things in order
    """

    setup_logging()
    args = parse_args(args)

    gen = args.settings_generator

    origin_source = parse_location(gen.get_source(args.origin))
    origin_backup_dir = gen.get_backup_dir(args.origin)

    with doing(f"Backing up {args.origin} to {origin_backup_dir}"):
        origin_archive = snapshot([f"{origin_source}", origin_backup_dir])

    target_backup_dir = gen.get_backup_dir(args.target)
    target_source = parse_location(gen.get_source(args.target))

    with doing(f"Checking if {args.target} ({target_source}) already exists"):
        target_exists = target_source.exists()

    if target_exists:
        with doing(f"Backing up {args.target} to {target_backup_dir}"):
            snapshot([f"{target_source}", target_backup_dir])

    if target_exists:
        with doing(f"Reading wp_config from {args.target}"):
            wp_config = parse_wp_config(target_source)
    else:
        with doing(f"Generating wp_config for {args.target}"):
            wp_config = gen.get_wp_config(args.target)

    patch = apply_wp_config(
        gen.get_patch(args.origin, args.target), wp_config, target_source
    )

    with NamedTemporaryFile(mode="w", encoding="utf-8") as pf:
        json.dump(patch, pf)
        pf.flush()

        with doing(f"Overriding {args.target} with {args.origin}"):
            restore(["-p", pf.name, f"{origin_archive}"])

    if hasattr(gen, "post_exec"):
        with doing("Running post-exec hook"):
            gen.post_exec(args.origin, args.target)


def __main__():
    return run_main(main, doing)


if __name__ == "__main__":
    __main__()
