import json
from abc import ABCMeta, abstractmethod
from collections import namedtuple

from datetime import datetime

from dateutil.relativedelta import relativedelta
from six import add_metaclass
from tzlocal import get_localzone

from app import avatar
from endpoints.api import format_date
from util.morecollections import AttrDict


class LogEntry(
  namedtuple('LogEntry', [
    'metadata_json', 'ip', 'datetime', 'performer_email', 'performer_username', 'performer_robot',
    'account_organization', 'account_username', 'account_email', 'account_robot', 'kind_id'
  ])):
  """
  LogEntry a single log entry.
  :type metadata_json: string
  :type ip: string
  :type datetime: string
  :type performer_email: int
  :type performer_username: string
  :type performer_robot: boolean
  :type account_organization: boolean
  :type account_username: string
  :type account_email: string
  :type account_robot: boolean
  :type kind_id: int
  """

  def to_dict(self, kinds, include_namespace):
    view = {
      'kind': kinds[self.kind_id],
      'metadata': json.loads(self.metadata_json),
      'ip': self.ip,
      'datetime': format_date(self.datetime),
    }

    if self.performer_username:
      performer = AttrDict({'username': self.performer_username, 'email': self.performer_email})
      performer.robot = None
      if self.performer_robot:
        performer.robot = self.performer_robot

      view['performer'] = {
        'kind': 'user',
        'name': self.performer_username,
        'is_robot': self.performer_robot,
        'avatar': avatar.get_data_for_user(performer),
      }

    if include_namespace:
      if self.account_username:
        account = AttrDict({'username': self.account_username, 'email': self.account_email})
        if self.account_organization:

          view['namespace'] = {
            'kind': 'org',
            'name': self.account_username,
            'avatar': avatar.get_data_for_org(account),
          }
        else:
          account.robot = None
          if self.account_robot:
            account.robot = self.account_robot
          view['namespace'] = {
            'kind': 'user',
            'name': self.account_username,
            'avatar': avatar.get_data_for_user(account),
          }

    return view


class LogEntryPage(
  namedtuple('LogEntryPage', ['logs', 'next_page_token'])):
  """
  LogEntryPage represents a single page of logs.
  :type logs: [LogEntry]
  :type next_page_token: {any -> any}
  """


class AggregatedLogEntry(
  namedtuple('AggregatedLogEntry', ['count', 'kind_id', 'day'])):
  """
  AggregatedLogEntry represents an aggregated view of logs.
  :type count: int
  :type kind_id: int
  :type day: string
  """
  def to_dict(self, kinds, start_time):
    synthetic_date = datetime(start_time.year, start_time.month, int(self.day), tzinfo=get_localzone())
    if synthetic_date.day < start_time.day:
      synthetic_date = synthetic_date + relativedelta(months=1)

    view = {
      'kind': kinds[self.kind_id],
      'count': self.count,
      'datetime': format_date(synthetic_date),
    }

    return view


@add_metaclass(ABCMeta)
class LogEntryDataInterface(object):
  """
  Interface that represents all data store interactions required by a Log.
  """

  @abstractmethod
  def get_logs_query(self, start_time, end_time, performer_name=None, repository_name=None, namespace_name=None,
                     ignore=None, page_token=None):
    """
    Returns a LogEntryPage.
    """

  @abstractmethod
  def get_log_entry_kinds(self):
    """
    Returns a map of LogEntryKind id -> name and name -> id
    """

  @abstractmethod
  def repo_exists(self, namespace_name, repository_name):
    """
    Returns whether or not a repo exists.
    """

  @abstractmethod
  def get_aggregated_logs(self, start_time, end_time, performer_name=None, repository_name=None, namespace_name=None,
                          ignore=None):
    """
    Returns a list of aggregated logs
    """