# -*- coding: utf-8 -*- #
# Copyright 2018 Google LLC. All Rights Reserved.
#
# 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
#
#    http://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.
"""Helper functions for interacting with the binauthz API."""


from apitools.base.py import list_pager
from googlecloudsdk.api_lib.util import apis

API_NAME = 'containeranalysis'

V1_ALPHA1 = 'v1alpha1'
V1_BETA1 = 'v1beta1'
V1 = 'v1'
DEFAULT_VERSION = V1


class Client(object):
  """A client to access containeranalysis for binauthz purposes."""

  def __init__(self, api_version=None):
    """Creates a ContainerAnalysisClient.

    Args:
      api_version: The containeranalysis API version to use.
    """
    self.api_version = api_version or DEFAULT_VERSION

    self.client = apis.GetClientInstance(API_NAME, self.api_version)
    self.messages = apis.GetMessagesModule(API_NAME, self.api_version)

  def YieldAttestations(
      self,
      note_ref=None,
      project_ref=None,
      artifact_digest=None,
      page_size=None,
      limit=None,
  ):
    """Yields occurrences associated with a given attestor Note or Project.

    Args:
      note_ref: The Note reference that will be queried for attached
        occurrences. If None, then all occurrences from the given project will
        be listed. (containeranalysis.projects.notes Resource)
      project_ref: The Project referenece that will be queried for occurrences
        if note_ref is None.
      artifact_digest: Digest of the artifact for which to fetch occurrences. If
        None, then all occurrences attached to the AA Note are returned.
      page_size: The number of attestations to retrieve per request. (If None,
        use the default page size.)
      limit: The maxium number of attestations to retrieve. (If None,
        unlimited.)

    Yields:
      Occurrences bound to `note_ref` with matching `artifact_digest` (if
      passed).
    """
    artifact_filter = (
        'has_suffix(resourceUrl, "{}")'.format(artifact_digest)
        if artifact_digest is not None
        else ''
    )
    if note_ref is None:
      service = self.client.projects_occurrences
      request = self.messages.ContaineranalysisProjectsOccurrencesListRequest(
          parent=project_ref.RelativeName(), filter=artifact_filter
      )
    else:
      service = self.client.projects_notes_occurrences
      request = (
          self.messages.ContaineranalysisProjectsNotesOccurrencesListRequest(
              name=note_ref.RelativeName(), filter=artifact_filter
          )
      )

    occurrence_iter = list_pager.YieldFromList(
        service=service,
        request=request,
        field='occurrences',
        batch_size=page_size or 100,  # Default batch_size.
        batch_size_attribute='pageSize',
        limit=limit,
    )

    # TODO(b/69380601): This should be handled by the filter parameter to
    # ListNoteOccurrences, but filtering isn't implemented yet for the fields
    # we care about.
    def MatchesFilter(occurrence):
      if (
          occurrence.kind
          != self.messages.Occurrence.KindValueValuesEnum.ATTESTATION
      ):
        return False
      return True

    for occurrence in occurrence_iter:
      if MatchesFilter(occurrence):
        yield occurrence

  def CreateAttestationOccurrence(
      self,
      note_ref,
      project_ref,
      artifact_url,
      public_key_id,
      signature,
      plaintext,
      validation_callback=None,
  ):
    """Creates an Occurrence referencing given URL and Note.

    This only supports the AttestationOccurrence-type Occurrence, currently only
    present in the v1 version of the API.

    Args:
      note_ref: The Note reference that the created Occurrence will be bound to.
        (containeranalysis.projects.notes Resource)
      project_ref: The project ref where the Occurrence will be created.
        (cloudresourcemanager.projects Resource)
      artifact_url: URL of artifact to which the signature is associated.
        (string)
      public_key_id: The URI of the public key that will be used to verify the
        signature. (string)
      signature: The content artifact's signature as generated by the specified
        key's signing operation. (string)
      plaintext: The content that was signed. (string)
      validation_callback: If provided, a function to validate the Occurrence
        prior to creation. (Callable[[Occurrence], None])

    Returns:
      Created Occurrence.
    """
    assert self.api_version == V1
    attestation = self.messages.AttestationOccurrence(
        serializedPayload=plaintext,
        signatures=[
            self.messages.Signature(
                publicKeyId=public_key_id, signature=signature
            ),
        ],
    )
    occurrence = self.messages.Occurrence(
        kind=self.messages.Occurrence.KindValueValuesEnum.ATTESTATION,
        resourceUri=artifact_url,
        noteName=note_ref.RelativeName(),
        attestation=attestation,
    )
    request = self.messages.ContaineranalysisProjectsOccurrencesCreateRequest(
        parent=project_ref.RelativeName(),
        occurrence=occurrence,
    )

    if validation_callback is not None:
      validation_callback(occurrence)

    return self.client.projects_occurrences.Create(request)
