# Copyright 2019 Google Inc. 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.
"""Timesketch API client library."""
from __future__ import unicode_literals

import copy
import os
import json
import logging

import pandas

from . import analyzer
from . import aggregation
from . import definitions
from . import error
from . import graph
from . import index as api_index
from . import resource
from . import search
from . import searchtemplate
from . import story
from . import timeline


logger = logging.getLogger('timesketch_api.sketch')


class Sketch(resource.BaseResource):
    """Timesketch sketch object.

    A sketch in Timesketch is a collection of one or more timelines. It has
    access control and its own namespace for things like labels and comments.

    Attributes:
        id: The ID of the sketch.
        api: An instance of TimesketchApi object.
    """

    # Add in necessary fields in data ingested via a different mechanism.
    _NECESSARY_DATA_FIELDS = frozenset([
        'timestamp', 'datetime', 'message'])

    def __init__(self, sketch_id, api, sketch_name=None):
        """Initializes the Sketch object.

        Args:
            sketch_id: Primary key ID of the sketch.
            api: An instance of TimesketchApi object.
            sketch_name: Name of the sketch (optional).
        """
        self.id = sketch_id
        self.api = api
        self._archived = None
        self._sketch_name = sketch_name
        super().__init__(api=api, resource_uri=f'sketches/{self.id}')

    @property
    def acl(self):
        """Property that returns back a ACL dict."""
        data = self.lazyload_data(refresh_cache=True)
        objects = data.get('objects')
        if not objects:
            return {}
        data_object = objects[0]
        permission_string = data_object.get('all_permissions')
        if not permission_string:
            return {}
        return json.loads(permission_string)

    @property
    def attributes(self):
        """Property that returns the sketch attributes."""
        data = self.lazyload_data(refresh_cache=True)
        meta = data.get('meta', {})
        return meta.get('attributes', {})

    @property
    def attributes_table(self):
        """Property that returns the sketch attributes as a data frame."""
        data = self.lazyload_data(refresh_cache=True)
        meta = data.get('meta', {})
        attributes = meta.get('attributes', [])

        data_frame = pandas.DataFrame(attributes)

        data_frame.columns = ['attribute', 'values', 'ontology']

        return data_frame

    @property
    def description(self):
        """Property that returns sketch description.

        Returns:
            Sketch description as string.
        """
        sketch = self.lazyload_data()
        return sketch['objects'][0]['description']

    @description.setter
    def description(self, description_value):
        """Change th
e sketch description to a new value."""
        if not isinstance(description_value, str):
            logger.error('Unable to change the name to a non string value')
            return

        resource_url = '{0:s}/sketches/{1:d}/'.format(
            self.api.api_root, self.id)

        data = {
            'description': description_value,
        }
        response = self.api.session.post(resource_url, json=data)
        _ = error.check_return_status(response, logger)

        # Force the new description to be re-loaded.
        _ = self.lazyload_data(refresh_cache=True)

    @property
    def labels(self):
        """Property that returns the sketch labels."""
        data = self.lazyload_data(refresh_cache=True)
        objects = data.get('objects', [])
        if not objects:
            return []

        sketch_data = objects[0]
        label_string = sketch_data.get('label_string', '')
        if label_string:
            return json.loads(label_string)

        return []

    @property
    def last_activity(self):
        """Property that returns the last activity.

        Returns:
            Sketch last activity as a string.
        """
        data = self.lazyload_data(refresh_cache=True)
        meta = data.get('meta', {})
        return meta.get('last_activity', '')

    @property
    def my_acl(self):
        """Property that returns back the ACL for the current user."""
        data = self.lazyload_data(refresh_cache=True)
        objects = data.get('objects')
        if not objects:
            return []
        data_object = objects[0]
        permission_string = data_object.get('my_permissions')
        if not permission_string:
            return []
        return json.loads(permission_string)

    @property
    def name(self):
        """Property that returns sketch name.

        Returns:
            Sketch name as string.
        """
        if not self._sketch_name:
            sketch = self.lazyload_data()
            self._sketch_name = sketch['objects'][0]['name']
        return self._sketch_name

    @name.setter
    def name(self, name_value):
        """Change the name of the sketch to a new value."""
        if not isinstance(name_value, str):
            logger.error('Unable to change the name to a non string value')
            return

        resource_url = '{0:s}/sketches/{1:d}/'.format(
            self.api.api_root, self.id)

        data = {
            'name': name_value,
        }
        response = self.api.session.post(resource_url, json=data)
        _ = error.check_return_status(response, logger)

        # Force the new name to be re-loaded.
        self._sketch_name = ''
        _ = self.lazyload_data(refresh_cache=True)

    @property
    def status(self):
        """Property that returns sketch status.

        Returns:
            Sketch status as string.
        """
        data = self.lazyload_data(refresh_cache=True)
        objects = data.get('objects')
        if not objects:
            return 'Unknown'

        if not isinstance(objects, (list, tuple)):
            return 'Unknown'

        first_object = objects[0]
        status_list = first_object.get('status')

        if not status_list:
            return 'Unknown'

        if len(status_list) < 1:
            return 'Unknown'

        return status_list[0].get('status', 'Unknown')

    def add_attribute_list(self, name, values, ontology='text'):
        """Adds or modifies attributes to the sketch.

        Args:
            name (str): The name of the attribute.
            values (list): A list of values (in their correct type according
                to the ontology).
            ontology (str): The ontology (matches with
                /data/ontology.yaml), which defines how the attribute
                is interpreted.

        Raises:
            ValueError: If any of the parameters are of the wrong type.

        Returns:
            A dict with the results from the operation.
        """
        if not isinstance(name, str):
            raise ValueError('Name needs to be a string.')

        if not isinstance(ontology, str):
            raise ValueError('Ontology needs to be a string.')

        resource_url = '{0:s}/sketches/{1:d}/attribute/'.format(
            self.api.api_root, self.id)

        data = {
            'name': name,
            'values': values,
            'ontology': ontology,
        }
        response = self.api.session.post(resource_url, json=data)

        status = error.check_return_status(response, logger)
        if not status:
            logger.error('Unable to add the attribute to the sketch.')

        return error.get_response_json(response, logger)

    def add_attribute(self, name, value, onto