"""
Represents a pipeline OIDC provider
"""
from abc import abstractmethod
from typing import List

import click

from samcli.commands.pipeline.bootstrap.guided_context import BITBUCKET, GITHUB_ACTIONS, GITLAB
from samcli.lib.config.samconfig import SamConfig


class PipelineOidcProvider:

    PROVIDER_URL_PARAMETER = "oidc-provider-url"
    CLIENT_ID_PARAMETER = "oidc-client-id"
    OPENID_CONNECT = "OpenID Connect (OIDC)"

    def __init__(self, oidc_parameters: dict, oidc_parameter_names: List[str], oidc_provider_name: str) -> None:
        self.oidc_parameters = oidc_parameters
        self.oidc_parameter_names = [self.PROVIDER_URL_PARAMETER, self.CLIENT_ID_PARAMETER] + oidc_parameter_names
        self.oidc_provider_name = oidc_provider_name
        self.verify_parameters()

    def verify_parameters(self) -> None:
        """
        Makes sure that all required parameters have been provided
        -------
        """
        error_string = ""
        for parameter_name in self.oidc_parameter_names:
            if not self.oidc_parameters[parameter_name]:
                error_string += f"Missing required parameter '--{parameter_name}'\n"
        if error_string:
            raise click.UsageError("\n" + error_string)

    def save_values(self, samconfig: SamConfig, cmd_names: List[str], section: str) -> None:
        """
        Saves provided values into config file so they can be reused for future calls to bootstrap
        """
        for parameter_name in self.oidc_parameter_names:
            samconfig.put(
                cmd_names=cmd_names,
                section=section,
                key=parameter_name.replace("-", "_"),
                value=self.oidc_parameters[parameter_name],
            )
        samconfig.put(cmd_names=cmd_names, section=section, key="oidc_provider", value=self.oidc_provider_name)
        samconfig.put(cmd_names=cmd_names, section=section, key="permissions_provider", value=self.OPENID_CONNECT)

    @abstractmethod
    def get_subject_claim(self) -> str:
        pass


class GitHubOidcProvider(PipelineOidcProvider):
    """
    Represents a GitHub Actions OIDC provider
    https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
    ----------
    subject_claim_parameters: dict
        Parameters specific to building the subject claim for this provider.
    oidc_parameters: dict
        Parameters common to all providers.
    """

    GITHUB_ORG_PARAMETER_NAME = "github-org"
    GITHUB_REPO_PARAMETER_NAME = "github-repo"
    DEPLOYMENT_BRANCH_PARAMETER_NAME = "deployment-branch"

    def __init__(self, subject_claim_parameters: dict, oidc_parameters: dict) -> None:
        all_oidc_parameters = {**oidc_parameters, **subject_claim_parameters}
        all_oidc_parameter_names = [
            self.GITHUB_ORG_PARAMETER_NAME,
            self.GITHUB_REPO_PARAMETER_NAME,
            self.DEPLOYMENT_BRANCH_PARAMETER_NAME,
        ]
        super().__init__(all_oidc_parameters, all_oidc_parameter_names, GITHUB_ACTIONS)

    def get_subject_claim(self) -> str:
        """
        Returns the subject claim that will be used to establish trust between the OIDC provider and AWS.
        To read more about OIDC claims see the following: https://openid.net/specs/openid-connect-core-1_0.html#Claims
        https://tinyurl.com/github-oidc-token
        In GitHubs case when using the official OIDC action to assume a role the audience claim will always be
        sts.amazon.aws so we must use the subject claim https://tinyurl.com/github-oidc-claim
        -------
        """
        org = self.oidc_parameters["github-org"]
        repo = self.oidc_parameters["github-repo"]
        branch = self.oidc_parameters["deployment-branch"]
        return f"repo:{org}/{repo}:ref:refs/heads/{branch}"


class GitLabOidcProvider(PipelineOidcProvider):
    """
    Represents a GitLab OIDC provider
    https://docs.gitlab.com/ee/integration/openid_connect_provider.html
    ----------
    subject_claim_parameters: dict
        Parameters specific to building the subject claim for this provider.
    oidc_parameters: dict
        Parameters common to all providers.
    """

    GITLAB_PROJECT_PARAMETER_NAME = "gitlab-project"
    GITLAB_GROUP_PARAMETER_NAME = "gitlab-group"
    DEPLOYMENT_BRANCH_PARAMETER_NAME = "deployment-branch"

    def __init__(self, subject_claim_parameters: dict, oidc_parameters: dict) -> None:
        all_oidc_parameters = {**oidc_parameters, **subject_claim_parameters}
        all_oidc_parameter_names = [
            self.GITLAB_PROJECT_PARAMETER_NAME,
            self.GITLAB_GROUP_PARAMETER_NAME,
            self.DEPLOYMENT_BRANCH_PARAMETER_NAME,
        ]
        super().__init__(all_oidc_parameters, all_oidc_parameter_names, GITLAB)

    def get_subject_claim(self) -> str:
        """
        Returns the subject claim that will be used to establish trust between the OIDC provider and AWS.
        To read more about OIDC claims see the following: https://openid.net/specs/openid-connect-core-1_0.html#Claims
        https://docs.gitlab.com/ee/ci/cloud_services/aws/#configure-a-role-and-trust
        To learn more about configuring a role to work with GitLab OIDC through claims see the following
        https://docs.gitlab.com/ee/ci/cloud_services/index.html#configure-a-conditional-role-with-oidc-claims
        -------
        """
        group = self.oidc_parameters["gitlab-group"]
        project = self.oidc_parameters["gitlab-project"]
        branch = self.oidc_parameters["deployment-branch"]
        return f"project_path:{group}/{project}:ref_type:branch:ref:{branch}"


class BitbucketOidcProvider(PipelineOidcProvider):
    """
    Represents a Bitbucket OIDC provider
    https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/
    ----------
    subject_claim_parameters: dict
        Parameters specific to building the subject claim for this provider.
    oidc_parameters: dict
        Parameters common to all providers.
    """

    BITBUCKET_REPO_UUID_PARAMETER_NAME = "bitbucket-repo-uuid"

    def __init__(self, subject_claim_parameters: dict, oidc_parameters: dict) -> None:
        all_oidc_parameters = {**oidc_parameters, **subject_claim_parameters}
        all_oidc_parameter_names = [
            self.BITBUCKET_REPO_UUID_PARAMETER_NAME,
        ]
        super().__init__(all_oidc_parameters, all_oidc_parameter_names, BITBUCKET)

    def get_subject_claim(self) -> str:
        """
        Returns the subject claim that will be used to establish trust between the OIDC provider and AWS.
        To read more about OIDC claims see the following: https://openid.net/specs/openid-connect-core-1_0.html#Claims
        To learn more about configuring a role to work with GitLab OIDC through claims see the following
        tinyurl.com/bitbucket-oidc-claims
        -------
        """
        repo_uuid = self.oidc_parameters[self.BITBUCKET_REPO_UUID_PARAMETER_NAME]
        return f"{repo_uuid}:*"
