import argparse
import dataclasses
import json
import os
import pathlib
import re
import subprocess
from typing import Optional


@dataclasses.dataclass
class CopybaraConfig:
    # Needed to run the additional updates for updating the rdev file
    mostrobotpy_local_repo_path: Optional[str] = None

    # Settings for migrating to a fork that you own
    mostrobotpy_fork_repo: Optional[str] = None
    allwpilib_fork_repo: Optional[str] = None
    robotpy_commandsv2_fork_repo: Optional[str] = None

    # Settings for truth repositories
    mostrobotpy_truth_repo: str = "https://github.com/robotpy/mostrobotpy.git"
    mostrobotpy_truth_branch: str = "2027"

    allwpilib_truth_repo: str = "https://github.com/wpilibsuite/allwpilib.git"
    allwpilib_truth_branch: str = "2027"


def run_copybara(
    copybara_file: pathlib.Path,
    migration: str,
    destination_url: str,
    force: bool,
    verbose: bool,
):
    args = [
        "bazel",
        "run",
        "//:copybara",
        "--",
        "migrate",
        str(copybara_file),
        migration,
        "--git-destination-url",
        destination_url,
        "--git-destination-non-fast-forward",
    ]

    if force:
        args.append("--force")
    if verbose:
        args.append("--verbose")

    subprocess.check_call(args)


def checkout_branch(auto_delete_branch: bool, branch_name: str):
    """
    This will attempt run a fetch on the repository in the cwd and checkout the given branch.

    If the branch currently exists locally, it will be deleted before the fetch happens.
    """
    # Check if the magic branch exists locally, and if so attempt to delete it
    ret = subprocess.call(["git", "rev-parse", "--verify", branch_name])
    branch_exists = ret == 0
    if branch_exists:
        if not auto_delete_branch:
            ans = input(f"Delete local branch {branch_name}?")
            if ans.lower() != "y":
                raise Exception(
                    f"You must delete your local copy of {branch_name} before the script can finish"
                )

        subprocess.check_call(["git", "branch", "-D", branch_name])

    subprocess.check_call(["git", "fetch", "--all"])
    subprocess.check_call(["git", "checkout", branch_name])


def update_mostrobotpy_rdev(wpilib_bin_version: str, is_development_build: bool):
    with open("rdev.toml") as f:
        contents = f.read()

    artifactory_path = "https://frcmaven.wpi.edu/artifactory/"
    if is_development_build:
        artifactory_path += "development-2027"
    else:
        artifactory_path += "release-2027"

    contents = re.sub(
        'wpilib_bin_version = ".*"',
        f'wpilib_bin_version = "{wpilib_bin_version}"',
        contents,
    )
    contents = re.sub(
        'wpilib_bin_url = ".*"', f'wpilib_bin_url = "{artifactory_path}"', contents
    )

    with open("rdev.toml", "w") as f:
        f.write(contents)

    subprocess.check_call(["./rdev.sh", "update-pyproject", "--commit"])


def allwpilib_to_mostrobotpy(
    copybara_file: pathlib.Path,
    mostrobotpy_local_repository: str,
    mostrobotpy_fork_repo: str,
    wpilib_bin_version: str,
    is_development_build: bool,
    auto_delete_branch: bool,
    force: bool,
    verbose: bool,
):
    run_copybara(
        copybara_file, "allwpilib_to_mostrobotpy", mostrobotpy_fork_repo, force, verbose
    )

    os.chdir(mostrobotpy_local_repository)
    checkout_branch(auto_delete_branch, "copybara_allwpilib_to_mostrobotpy")
    update_mostrobotpy_rdev(wpilib_bin_version, is_development_build)

    # Run black
    subprocess.check_call(["black", "."])
    subprocess.check_call(["git", "add", "-A"])
    subprocess.call(["git", "commit", "-m", "Run black"])

    subprocess.check_call(["git", "push", "-f"])


def mostrobotpy_to_allwpilib(
    copybara_file: pathlib.Path, allwpilib_fork, force: bool, verbose: bool
):
    run_copybara(
        copybara_file, "mostrobotpy_to_allwpilib", allwpilib_fork, force, verbose
    )


def load_user_config() -> CopybaraConfig:
    script_dir = pathlib.Path(__file__).parent
    user_config_file = script_dir / ".copybara.json"
    if os.path.exists(user_config_file):
        print(f"Loading user config from '{user_config_file}'")
        with open(user_config_file, "r") as f:
            json_config = json.load(f)
    else:
        print(
            f"No user config present at '{user_config_file}', no defaults will be loaded"
        )
        json_config = {}

    return CopybaraConfig(**json_config)


def main():
    user_config = load_user_config()

    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(
        dest="migration", required=True, help="Available commands"
    )

    parser.add_argument("--force", action="store_true")
    parser.add_argument("--verbose", action="store_true")

    def add_allwpilib_fork_arg(subparser):
        subparser.add_argument(
            "--allwpilib_fork_repo",
            default=user_config.allwpilib_fork_repo,
            help="URL to your github fork of allwpilib that you have write permissions for",
        )

    # allwpilib -> mostrobotpy
    allwpilib_to_mostrobotpy_parser = subparsers.add_parser(
        "allwpilib_to_mostrobotpy",
        help="Pushes changes from the allwpilib mirror to mostrobotpy",
    )
    allwpilib_to_mostrobotpy_parser.add_argument(
        "--wpilib_bin_version",
        required=True,
        type=str,
        help="The wpilib release version as hosted on artifactory",
    )
    allwpilib_to_mostrobotpy_parser.add_argument(
        "--development_build",
        action="store_true",
        help="True if you are upgrading to a development build instead of a release build. Affects where artifacts will be downloaded from.",
    )
    allwpilib_to_mostrobotpy_parser.add_argument(
        "--mostrobotpy_local_repo_path",
        default=user_config.mostrobotpy_local_repo_path,
        help="Path on your local computer to a mostrobotpy clone",
    )
    allwpilib_to_mostrobotpy_parser.add_argument(
        "--mostrobotpy_fork_repo",
        default=user_config.mostrobotpy_fork_repo,
        help="URL to your github fork of mostrobotpy that you have write permissions for",
    )
    allwpilib_to_mostrobotpy_parser.add_argument(
        "-y",
        "--auto_delete_branch",
        action="store_true",
        help="If present, will automatically delete the local version of the copybara branch if it exists. Otherwise you will be prompted if it is ok to delete",
    )

    # mostrobotpy -> allwpilib
    mostrobotpy_to_allwpilib_parser = subparsers.add_parser(
        "mostrobotpy_to_allwpilib",
        help="Pulls changes from the mostrobotpy source of truth to this mirror",
    )
    add_allwpilib_fork_arg(mostrobotpy_to_allwpilib_parser)

    script_dir = pathlib.Path(__file__).parent
    copybara_file = script_dir / "../../../copy.bara.sky"

    args = parser.parse_args()

    if args.migration == "allwpilib_to_mostrobotpy":
        if args.mostrobotpy_local_repo_path is None:
            raise Exception(
                "You mist specify mostrobotpy_local_repo_path, either on the command line or in your user config"
            )
        if args.mostrobotpy_fork_repo is None:
            raise Exception(
                "You mist specify mostrobotpy_fork_repo, either on the command line or in your user config"
            )
        allwpilib_to_mostrobotpy(
            copybara_file,
            args.mostrobotpy_local_repo_path,
            args.mostrobotpy_fork_repo,
            args.wpilib_bin_version,
            args.development_build,
            args.auto_delete_branch,
            args.force,
            args.verbose,
        )
    elif args.migration == "mostrobotpy_to_allwpilib":
        if args.allwpilib_fork_repo is None:
            raise Exception(
                "You mist specify allwpilib_fork_repo, either on the command line or in your user config"
            )
        mostrobotpy_to_allwpilib(
            copybara_file, args.allwpilib_fork_repo, args.force, args.verbose
        )
    else:
        raise Exception(f"Unexpected migration {args.migration}")


if __name__ == "__main__":
    main()
