import json
import logging
import time

from collections import defaultdict

import features

from app import secscan_notification_queue, secscan_api
from data import model
from data.model.tag import filter_tags_have_repository_event, get_matching_tags
from data.database import (Image, ImageStorage, ExternalNotificationEvent,
                           Repository, RepositoryNotification, RepositoryTag)
from endpoints.notificationhelper import spawn_notification
from workers.queueworker import QueueWorker


logger = logging.getLogger(__name__)


class SecurityNotificationWorker(QueueWorker):
  def process_queue_item(self, data):
    cve_id = data['Name']
    vulnerability = data['Content']['Vulnerability']
    priority = vulnerability['Priority']

    # Lookup the external event for when we have vulnerabilities.
    event = ExternalNotificationEvent.get(name='vulnerability_found')

    # For each layer, retrieving the matching tags and join with repository to determine which
    # require new notifications.
    tag_map = defaultdict(set)
    repository_map = {}

    # Find all tags that contain the layer(s) introducing the vulnerability,
    # in repositories that have the event setup.
    content = data['Content']
    layer_ids = content.get('NewIntroducingLayersIDs', content.get('IntroducingLayersIDs', []))
    for layer_id in layer_ids:
      (docker_image_id, storage_uuid) = layer_id.split('.', 2)

      matching = get_matching_tags(docker_image_id, storage_uuid, RepositoryTag, Repository,
                                   Image, ImageStorage)
      tags = list(filter_tags_have_repository_event(matching, event))

      check_map = {}
      for tag in tags:
        # Verify that the tag's root image has the vulnerability.
        tag_layer_id = '%s.%s' % (tag.image.docker_image_id, tag.image.storage.uuid)
        logger.debug('Checking if layer %s is vulnerable to %s', tag_layer_id, cve_id)

        if not tag_layer_id in check_map:
          is_vulerable = secscan_api.check_layer_vulnerable(tag_layer_id, cve_id)
          check_map[tag_layer_id] = is_vulerable

        logger.debug('Result of layer %s is vulnerable to %s check: %s', tag_layer_id, cve_id,
                     check_map[tag_layer_id])

        if check_map[tag_layer_id]:
          # Add the vulnerable tag to the list.
          tag_map[tag.repository_id].add(tag.name)
          repository_map[tag.repository_id] = tag.repository

    # For each of the tags found, issue a notification.
    for repository_id in tag_map:
      tags = tag_map[repository_id]
      event_data = {
        'tags': list(tags),
        'vulnerability': {
          'id': data['Name'],
          'description': vulnerability['Description'],
          'link': vulnerability['Link'],
          'priority': priority,
        },
      }

      spawn_notification(repository_map[repository_id], 'vulnerability_found', event_data)


if __name__ == '__main__':
  if not features.SECURITY_SCANNER:
    logger.debug('Security scanner disabled; skipping SecurityNotificationWorker')
    while True:
      time.sleep(100000)

  worker = SecurityNotificationWorker(secscan_notification_queue, poll_period_seconds=30,
                                      reservation_seconds=30, retry_after_seconds=30)
  worker.start()