# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides the speed releasing table."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import

import collections
import json
import six.moves.urllib.parse

from google.appengine.ext import ndb

from dashboard import alerts
from dashboard.common import datastore_hooks
from dashboard.common import request_handler
from dashboard.common import utils
from dashboard.models import anomaly
from dashboard.models import graph_data
from dashboard.models import table_config

# These represent the revision ranges per milestone. For Clank, this is a
# point id, for Chromium this is a Chromium commit position.
CLANK_MILESTONES = {
    54: (1473196450, 1475824394),
    55: (1475841673, 1479536199),
    56: (1479546161, 1485025126),
    57: (1486119399, 1488528859),
    58: (1488538235, 1491977185),
    59: (1492542658, 1495792284),
    60: (1495802833, 1500610872),
    61: (1500628339, 1504160258),
    62: (1504294629, 1507887190),
    63: (1507887190, 1512141580),
    64: (1512154460, 1516341121),
    65: (1516353162, 1519951206),
    66: (1519963059, 1523653340),
    67: (1523629648
, None),
}

CHROMIUM_MILESTONES = {
    54: (416640, 423768),
    55: (433391, 433400),
    56: (433400, 445288),
    57: (447949, 454466),
    58: (454523, 463842),
    59: (465221, 474839),
    60: (474952, 488392)
,
    61: (488576, 498621),
    62: (499187, 508578),
    63: (508578, 520719),
    64: (520917, 530282),
    65: (530373, 540240),
    66: (540302, 550534),
    67: (554148, None),
}

CURRENT_MILESTONE = max(CHROMIUM_MILESTONES.keys())


class SpeedReleasingHandler(request_handler.RequestHandler):
  """Request handler for requests for speed releasing page."""

  def get(self, *args):  # pylint: disable=unused-argument
    """Renders the UI for the speed releasing page."""
    self.RenderStaticHtml('speed_releasing.html')

  def post(self, *args):
    """Returns dynamic data for /speed_releasing.

    Args:
      args: May contain the table_name for the requested Speed Releasing
            report. If args is empty, user is requesting the Speed Releasing
            landing page.
    Requested parameters:
      anomalies: A boolean that is set if the POST request is for the Release
                 Notes alerts-table. Note, the table_name must also be passed
                 in (via args) to retrieve the correct set of data.
    Outputs:
      JSON for the /speed_releasing page XHR request.
    """
    anomalies = self.request.get('anomalies')
    if args[0] and not anomalies:
      self._OutputTableJSON(args[0])
    elif args[0]:
      self._OutputAnomaliesJSON(args[0])
    else:
      self._OutputHomePageJSON()

  def _OutputTableJSON(self, table_name):
    """Obtains the JSON values that comprise the table.

    Args:
      table_name: The name of the requested report.
    """
    table_entity = ndb.Key('TableConfig', table_name).get()
    if not table_entity:
      self.response.out.write(json.dumps({'error': 'Invalid table name.'}))
      return

    rev_a = self.request.get('revA')
    rev_b = self.request.get('revB')
    milestone_param = self.request.get('m')

    if milestone_param:
      milestone_param = int(milestone_param)
      if milestone_param not in CHROMIUM_MILESTONES:
        self.response.out.write(
            json.dumps({'error': 'No data for that milestone.'}))
        return

    master_bot_pairs = _GetMasterBotPairs(table_entity.bots)
    rev_a, rev_b, milestone_dict = _GetRevisionsFromParams(
        rev_a, rev_b, milestone_param, table_entity, master_bot_pairs)

    revisions = [rev_b, rev_a]  # In reverse intentionally. This is to support
    # the format of the Chrome Health Dashboard which compares 'Current' to
    # 'Reference', in that order. The ordering here is for display only.
    display_a = _GetDisplayRev(master_bot_pairs, table_entity.tests, rev_a)
    display_b = _GetDisplayRev(master_bot_pairs, table_entity.tests, rev_b)

    display_milestone_a, display_milestone_b = _GetMilestoneForRevs(
        rev_a, rev_b, milestone_dict)
    navigation_milestone_a, navigation_milestone_b = _GetNavigationMilestones(
        display_milestone_a, display_milestone_b, milestone_dict)

    values = {}
    self.GetDynamicVariables(values)
    self.response.out.write(
        json.dumps({
            'xsrf_token':
                values['xsrf_token'],
            'table_bots':
                master_bot_pairs,
            'table_tests':
                table_entity.tests,
            'table_layout':
                json.loads(table_entity.table_layout),
            'name':
                table_entity.key.string_id(),
            'values':
                _GetRowValues(revisions, master_bot_pairs, table_entity.tests),
            'units':
                _GetTestToUnitsMap(master_bot_pairs, table_entity.tests),
            'revisions':
                revisions,
            'categories':
                _GetCategoryCounts(json.loads(table_entity.table_layout)),
            'urls':
                _GetDashboardURLMap(master_bot_pairs, table_entity.tests, rev_a,
                                    rev_b),
            'display_revisions': [display_b,
                                  display_a],  # Similar to revisions.
            'display_milestones': [display_milestone_a, display_milestone_b],
            'navigation_milestones': [
                navigation_milestone_a, navigation_milestone_b
            ]
        }))

  def _OutputHomePageJSON(self):
    """Returns a list of reports a user has permission to see."""
    all_entities = table_config.TableConfig.query().fetch()
    list_of_entities = []
    for entity in all_entities:
      list_of_entities.append(entity.key.string_id())
    self.response.out.write(
        json.dumps({
            'show_list': True,
            'list': list_of_entities
        }))

  def _OutputAnomaliesJSON(self, table_name):
    """Obtains the entire alert list specified.

    Args:
      table_name: The name of the requested report.
    """
    table_entity = ndb.Key('TableConfig', table_name).get()
    if not table_entity:
      self.response.out.write(json.dumps({'error': 'Invalid table name.'}))
      return
    rev_a = self.request.get('revA')
    rev_b = self.request.get('revB')
    milestone_param = self.request.get('m')

    if milestone_param:
      milestone_param = int(milestone_param)
      if milestone_param not in CHROMIUM_MILESTONES:
        self.response.out.write(
            json.dumps({'error': 'No data for that milestone.'}))
        return

    master_bot_pairs = _GetMasterBotPairs(table_entity.bots)
    rev_a, rev_b, _ = _GetRevisionsFromParams(rev_a, rev_b, milestone_param,
                                              table_entity, master_bot_pairs)
    revisions = [rev_b, rev_a]

    anomalies = _FetchAnomalies(table_entity, rev_a, rev_b)
    anomaly_dicts = alerts.AnomalyDicts(anomalies)

    values = {}
    self.GetDynamicVariables(values)
    self.response.out.write(
        json.dumps({
            'xsrf_token': values['xsrf_token'],
            'revisions': revisions,
            'anomalies': anomaly_dicts
        }))


def _GetRevisionsFromParams(rev_a, rev_b, milestone_param, table_entity,
                            master_bot_pairs):
  milestone_dict = _GetUpdatedMilestoneDict(master_bot_pairs,
                                            table_entity.tests)
  if milestone_param:
    rev_a, rev_b = milestone_dict[milestone_param]
  if not rev_a or not rev_b:  # If no milestone param and <2 revs passed in.
    rev_a, rev_b = _GetEndRevOrCurrentMilestoneRevs(rev_a, rev_b,
                                                    milestone_dict)
  rev_a, rev_b = _CheckRevisions(rev_a, rev_b)
  return rev_a, rev_b, milestone_dict


def _GetMasterBotPairs(bots):
  master_bot_pairs = []
  for bot in bots:
    master_bot_pairs.append(bot.parent().string_id() + '/' + bot.string_id())
  return master_bot_pairs


def _GetRowValues(revisions, bots, tests):
  "