# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
from typing import Optional, Sequence, Tuple
from urllib import parse

import attr
import click
import pyperclip
from dateutil import tz
import requests

import releasetool.filehelpers
import releasetool.git
import releasetool.github
import releasetool.secrets


@attr.s(auto_attribs=True, slots=True)
class BaseContext:
    interactive: bool = True


@attr.s(auto_attribs=True, slots=True)
class GitHubContext(BaseContext):
    github: Optional[releasetool.github.GitHub] = None
    origin_user: Optional[str] = None
    origin_repo: Optional[str] = None
    upstream_name: Optional[str] = None
    upstream_repo: Optional[str] = None
    package_name: Optional[str] = None
    changes: Sequence[str] = ()
    release_notes: Optional[str] = None


@attr.s(auto_attribs=True, slots=True)
class TagContext(GitHubContext):
    release_pr: Optional[dict] = None
    release_tag: Optional[str] = None
    release_version: Optional[str] = None
    github_release: Optional[dict] = None
    kokoro_job_name: Optional[str] = None
    fusion_url: Optional[str] = None


def _determine_origin(ctx: GitHubContext) -> None:
    remotes = releasetool.git.get_github_remotes()
    origin = remotes["origin"]
    ctx.origin_user = origin.split("/")[0]
    ctx.origin_repo = origin


def _determine_upstream(ctx: GitHubContext, owners: Tuple[str, ...]) -> None:
    remotes = releasetool.git.get_github_remotes()
    repos = {
        name: repo for name, repo in remotes.items() if repo.lower().startswith(owners)
    }

    if not repos:
        raise ValueError("Unable to determine the upstream GitHub repo. :(")

    if len(repos) > 1:
        options = "\n".join(f"  * {name}: {repo}" for name, repo in repos.items())
        choice = click.prompt(
            click.style(
                f"More than one possible upstream remote was found."
                f"\n\n{options}\n\n"
                f"Please enter the *name* of one you want to use",
                fg="yellow",
            ),
            default="origin",
        )
        try:
            upstream = choice, repos[choice]
        except KeyError:
            click.secho(f"No remote named {choice}!", fg="red")
            raise click.Abort()
    else:
        upstream = repos.popitem()

    ctx.upstream_name, ctx.upstream_repo = upstream


def setup_github_context(
    ctx: GitHubContext,
    owners: Tuple[str, ...] = ("googlecloudplatform", "googleapis", "google"),
) -> None:
    click.secho("> Determining GitHub context.", fg="cyan")
    github_token = releasetool.secrets.ensure_password(
        "github",
        "Please provide your GitHub API token with write:repo_hook and "
        "public_repo (https://github.com/settings/tokens)",
    )
    ctx.github = releasetool.github.GitHub(github_token)

    _determine_origin(ctx)
    _determine_upstream(ctx, owners)

    click.secho(f"Origin: {ctx.origin_repo}, Upstream: {ctx.upstream_repo}")

    # Compare upstream/master with master
    click.secho(f"> Comparing {ctx.upstream_name}/master to master.", fg="cyan")

    if releasetool.git.get_latest_commit(
        f"{ctx.upstream_name}/master"
    ) == releasetool.git.get_latest_commit("master"):
        click.echo(f"master is up to date with {ctx.upstream_name}/master")
    else:
        click.secho(
            f"WARNING: master is not up to date with {ctx.upstream_name}/master",
            fg="red",
        )
        if click.confirm("> Would you like to continue?") is False:
            exit()


def edit_release_notes(ctx: GitHubContext) -> None:
    click.secho("> Opening your editor to finalize release notes.", fg="cyan")
    release_notes = (
        datetime.datetime.now(datetime.timezone.utc)
        .astimezone(tz.gettz("US/Pacific"))
        .strftime("%m-%d-%Y %H:%M %Z\n\n")
    )
    release_notes += "\n".join(f"- {change}" for change in ctx.changes)
    release_notes += "\n\n### ".join(
        [
            "",
            "Implementation Changes",
            "New Features",
            "Dependencies",
            "Documentation",
            "Internal / Testing Changes",
        ]
    )
    ctx.release_notes = releasetool.filehelpers.open_editor_with_tempfile(
        release_notes, "release-notes.md"
    ).strip()


def release_exists(ctx: TagContext) -> bool:
    try:
        release = ctx.github.get_release(ctx.upstream_repo, ctx.release_tag)
        tag_sha = ctx.github.get_tag_sha(ctx.upstream_repo, ctx.release_tag)
    # If a 404 or similar happened, return False as it indicates the release
    # doesn't exit.
    except requests.HTTPError:
        return False

    if release is not None and tag_sha == ctx.release_pr["merge_commit_sha"]:
        return True
    else:
        return False


def publish_via_kokoro(ctx: TagContext) -> None:
    kokoro_url = "https://fusion.corp.google.com/projectanalysis/current/KOKORO/"

    ctx.fusion_url = parse.urljoin(
        kokoro_url, parse.quote_plus(f"prod:{ctx.kokoro_job_name}")
    )

    if ctx.interactive:
        pyperclip.copy(ctx.release_tag)

        click.secho(
            "> Trigger the Kokoro build with the commitish below to publish to PyPI. The commitish has been copied to the clipboard.",
            fg="cyan",
        )

    click.secho(f"Kokoro build URL:\t\t{click.style(ctx.fusion_url, underline=True)}")
    click.secho(f"Commitish:\t{click.style(ctx.release_tag, bold=True)}")

    if ctx.interactive:
        if click.confirm("Would you like to go the Kokoro build page?", default=True):
            click.launch(ctx.fusion_url)