# -*- coding: utf-8 -*-

# Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
#
# 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.

import logging
import paginate
import sqlalchemy as sa
import appenlight.lib.helpers as h

from datetime import datetime

from appenlight.models import get_db_session, Datastores
from appenlight.models.report import Report
from appenlight.models.report_group import ReportGroup
from appenlight.models.report_comment import ReportComment
from appenlight.models.user import User
from appenlight.models.services.base import BaseService
from appenlight.lib.enums import ReportType
from appenlight.lib.utils import es_index_name_limiter

log = logging.getLogger(__name__)


class ReportGroupService(BaseService):
    @classmethod
    def get_trending(cls, request, filter_settings, limit=15, db_session=None):
        """
        Returns report groups trending for specific time interval
        """
        db_session = get_db_session(db_session)

        tags = []
        if filter_settings.get("tags"):
            for tag in filter_settings["tags"]:
                tags.append(
                    {"terms": {"tags.{}.values".format(tag["name"]): tag["value"]}}
                )

        index_names = es_index_name_limiter(
            start_date=filter_settings["start_date"],
            end_date=filter_settings["end_date"],
            ixtypes=["reports"],
        )

        if not index_names or not filter_settings["resource"]:
            return []

        es_query = {
            "aggs": {
                "parent_agg": {
                    "aggs": {
                        "groups": {
                            "aggs": {
                                "sub_agg": {
                                    "value_count": {
                                        "field": "tags.group_id.values.keyword"
                                    }
                                }
                            },
                            "filter": {"exists": {"field": "tags.group_id.values"}},
                        }
                    },
                    "terms": {"field": "tags.group_id.values.keyword", "size": limit},
                }
            },
            "query": {
                "bool": {
                    "filter": [
                        {"terms": {"resource_id": [filter_settings["resource"][0]]}},
                        {
                            "range": {
                                "timestamp": {
                                    "gte": filter_settings["start_date"],
                                    "lte": filter_settings["end_date"],
                                }
                            }
                        },
                    ]
                }
            },
        }
        if tags:
            es_query["query"]["bool"]["filter"].extend(tags)

        result = Datastores.es.search(
            body=es_query, index=index_names, doc_type="report", size=0
        )
        series = []
        for bucket in result["aggregations"]["parent_agg"]["buckets"]:
            series.append(
                {"key": bucket["key"], "groups": bucket["groups"]["sub_agg"]["value"]}
            )

        report_groups_d = {}
        for g in series:
            report_groups_d[int(g["key"])] = g["groups"] or 0

        query = db_session.query(ReportGroup)
        query = query.filter(ReportGroup.id.in_(list(report_groups_d.keys())))
        query = query.options(sa.orm.joinedload(ReportGroup.last_report_ref))
        results = [(report_groups_d[group.id], group) for group in query]
        return sorted(results, reverse=True, key=lambda x: x[0])

    @classmethod
    def get_search_iterator(
        cls,
        app_ids=None,
        page=1,
        items_per_page=50,
        order_by=None,
        filter_settings=None,
        limit=None,
    ):
        if not app_ids:
            return {}
        if not filter_settings:
            filter_settings = {}

        query = {
            "size": 0,
            "query": {
                "bool": {
                    "must": [],
                    "should": [],
                    "filter": [{"terms": {"resource_id": list(app_ids)}}],
                }
            },
            "aggs": {
                "top_groups": {
                    "terms": {
                        "size": 5000,
                        "field": "join_field#report_group",
                        "order": {"newest": "desc"},
                    },
                    "aggs": {
                        "top_reports_hits": {
                            "top_hits": {"size": 1, "sort": {"start_time": "desc"}}
                        },
                        "newest": {"max": {"field": "start_time"}},
                    },
                }
            },
        }

        start_date = filter_settings.get("start_date")
        end_date = filter_settings.get("end_date")
        filter_part = query["query"]["bool"]["filter"]
        date_range = {"range": {"start_time": {}}}
        if start_date:
            date_range["range"]["start_time"]["gte"] = start_date
        if end_date:
            date_range["range"]["start_time"]["lte"] = end_date
        if start_date or end_date:
            filter_part.append(date_range)

        priorities = filter_settings.get("priority")

        for tag in filter_settings.get("tags", []):
            tag_values = [v.lower() for v in tag["value"]]
            key = "tags.%s.values" % tag["name"].replace(".", "_")
            filter_part.append({"terms": {key: tag_values}})

        if priorities:
            filter_part.append(
                {
                    "has_parent": {
                        "parent_type": "report_group",
                        "query": {"terms": {"priority": priorities}},
                    }
                }
            )

        min_occurences = filter_settings.get("min_occurences")
        if min_occurences:
            filter_part.append(
                {
                    "has_parent": {
                        "parent_type": "report_group",
                        "query": {"range": {"occurences": {"gte": min_occurences[0]}}},
                    }
                }
            )

        min_duration = filter_settings.get("min_duration")
        max_duration = filter_settings.get("max_duration")

        request_ids = filter_settings.get("request_id")
        if request_ids:
            filter_part.append({"terms": {"request_id": request_ids}})

        duration_range = {"range": {"average_duration": {}}}
        if min_duration:
            duration_range["range"]["average_duration"]["gte"] = min_duration[0]
        if max_duration:
            duration_range["range"]["average_duration"]["lte"] = max_duration[0]
        if min_duration or max_duration:
            filter_part.append(
                {"has_parent": {"parent_type": "report_group", "query": duration_range}}
            )

        http_status = filter_settings.get("http_status")
        report_type = filter_settings.get("report_type", [ReportType.error])
        # set error report type if http status is not found
        # and we are dealing with slow reports
        if not http_status or ReportType.slow in report_type:
            filter_part.append({"terms": {"report_type": report_type}})
        if http_status:
            filter_part.append({"terms": {"http_status": http_status}})

        messages = filter_settings.get("message")
        if messages:
            condition = {"match": {"message": " ".join(messages)}}
            query["query"]["bool"]["must"].append(condition)
        errors = filter_settings.get("error")
        if errors:
            condition = {"match": {"error": " ".join(errors)}}
            query["query"]["bool"]["must"].append(condition)
        url_domains = filter_settings.get("url_domain")
        if url_domains:
            condition = {"terms": {"url_domain": url_domains}}
            query["query"]["bool"]["must"].append(condition)
        url_paths = filter_settings.get("url_path")
        if url_paths:
            condition = {"terms": {"url_path": url_paths}}
            query["query"]["bool"]["must"].append(condition)

        if filter_settings.get("report_status"):
            for status in filter_settings.get("report_status"):
                if status == "never_reviewed":
                    filter_part.append(
                        {
                            "has_parent": {
                                "parent_type": "report_group",
                                "query": {"term": {"read": False}},
                            }
                        }
                    )
                elif status == "reviewed":
                    filter_part.append(
                        {
                            "has_parent": {
                                "parent_type": "report_group",
                                "query": {"term": {"read": True}},
                            }
                        }
                    )
                elif status == "public":
                    filter_part.append(
                        {
                            "has_parent": {
                                "parent_type": "report_group",
                                "query": {"term": {"public": True}},
                            }
                        }
                    )
                elif status == "fixed":
                    filter_part.append(
                        {
                            "has_parent": {
                                "parent_type": "report_group",
                                "query": {"term": {"fixed": True}},
                            }
                        }
                    )

        # logging.getLogger('pyelasticsearch').setLevel(logging.DEBUG)
        index_names = es_index_name_limiter(
            filter_settings.get("start_date"),
            filter_settings.get("end_date"),
            ixtypes=["reports"],
        )
        if index_names:
            results = Datastores.es.search(
                body=query,
                index=index_names,
                doc_type=["report", "report_group"],
                size=0,
            )
        else:
            return []
        return results["aggregations"]

    @classmethod
    def get_paginator_by_app_ids(
        cls,
        app_ids=None,
        page=1,
        item_count=None,
        items_per_page=50,
        order_by=None,
        filter_settings=None,
        exclude_columns=None,
        db_session=None,
    ):
        if not filter_settings:
            filter_settings = {}
        results = cls.get_search_iterator(
            app_ids, page, items_per_page, order_by, filter_settings
        )

        ordered_ids = []
        if results:
            for item in results["top_groups"]["buckets"]:
                pg_id = item["top_reports_hits"]["hits"]["hits"][0]["_source"][
                    "report_id"
                ]
                ordered_ids.append(pg_id)
        log.info(filter_settings)
        paginator = paginate.Page(
            ordered_ids, items_per_page=items_per_page, **filter_settings
        )
        sa_items = ()
        if paginator.items:
            db_session = get_db_session(db_session)
            # latest report detail
            query = db_session.query(Report)
            query = query.options(sa.orm.joinedload(Report.report_group))
            query = query.filter(Report.id.in_(paginator.items))
            if filter_settings.get("order_col"):
                order_col = filter_settings.get("order_col")
                if filter_settings.get("order_dir") == "dsc":
                    sort_on = "desc"
                else:
                    sort_on = "asc"
                if order_col == "when":
                    order_col = "last_timestamp"
                query = query.order_by(
                    getattr(sa, sort_on)(getattr(ReportGroup, order_col))
                )
            sa_items = query.all()
        sorted_instance_list = []
        for i_id in ordered_ids:
            for report in sa_items:
                if str(report.id) == i_id and report not in sorted_instance_list:
                    sorted_instance_list.append(report)
        paginator.sa_items = sorted_instance_list
        return paginator

    @classmethod
    def by_app_ids(cls, app_ids=None, order_by=True, db_session=None):
        db_session = get_db_session(db_session)
        q = db_session.query(ReportGroup)
        if app_ids:
            q = q.filter(ReportGroup.resource_id.in_(app_ids))
        if order_by:
            q = q.order_by(sa.desc(ReportGroup.id))
        return q

    @classmethod
    def by_id(cls, group_id, app_ids=None, db_session=None):
        db_session = get_db_session(db_session)
        q = db_session.query(ReportGroup).filter(ReportGroup.id == int(group_id))
        if app_ids:
            q = q.filter(ReportGroup.resource_id.in_(app_ids))
        return q.first()

    @classmethod
    def by_ids(cls, group_ids=None, db_session=None):
        db_session = get_db_session(db_session)
        query = db_session.query(ReportGroup)
        query = query.filter(ReportGroup.id.in_(group_ids))
        return query

    @classmethod
    def by_hash_and_resource(
        cls, resource_id, grouping_hash, since_when=None, db_session=None
    ):
        db_session = get_db_session(db_session)
        q = db_session.query(ReportGroup)
        q = q.filter(ReportGroup.resource_id == resource_id)
        q = q.filter(ReportGroup.grouping_hash == grouping_hash)
        q = q.filter(ReportGroup.fixed == False)
        if since_when:
            q = q.filter(ReportGroup.first_timestamp >= since_when)
        return q.first()

    @classmethod
    def users_commenting(cls, report_group, exclude_user_id=None, db_session=None):
        db_session = get_db_session(None, report_group)
        query = db_session.query(User).distinct()
        query = query.filter(User.id == ReportComment.owner_id)
        query = query.filter(ReportComment.group_id == report_group.id)
        if exclude_user_id:
            query = query.filter(ReportComment.owner_id != exclude_user_id)
        return query

    @classmethod
    def affected_users_count(cls, report_group, db_session=None):
        db_session = get_db_session(db_session)
        query = db_session.query(sa.func.count(Report.username))
        query = query.filter(Report.group_id == report_group.id)
        query = query.filter(Report.username != "")
        query = query.filter(Report.username != None)
        query = query.group_by(Report.username)
        return query.count()

    @classmethod
    def top_affected_users(cls, report_group, db_session=None):
        db_session = get_db_session(db_session)
        count_label = sa.func.count(Report.username).label("count")
        query = db_session.query(Report.username, count_label)
        query = query.filter(Report.group_id == report_group.id)
        query = query.filter(Report.username != None)
        query = query.filter(Report.username != "")
        query = query.group_by(Report.username)
        query = query.order_by(sa.desc(count_label))
        query = query.limit(50)
        return query

    @classmethod
    def get_report_stats(cls, request, filter_settings):
        """
        Gets report dashboard graphs
        Returns information for BAR charts with occurences/interval information
        detailed means version that returns time intervals - non detailed
        returns total sum
        """
        delta = filter_settings["end_date"] - filter_settings["start_date"]
        if delta < h.time_deltas.get("12h")["delta"]:
            interval = "1m"
        elif delta <= h.time_deltas.get("3d")["delta"]:
            interval = "5m"
        elif delta >= h.time_deltas.get("2w")["delta"]:
            interval = "24h"
        else:
            interval = "1h"

        group_id = filter_settings.get("group_id")

        es_query = {
            "aggs": {
                "parent_agg": {
                    "aggs": {
                        "types": {
                            "aggs": {
                                "sub_agg": {
                                    "terms": {"field": "tags.type.values.keyword"}
                                }
                            },
                            "filter": {
                                "bool": {
                                    "filter": [
                                        {"exists": {"field": "tags.type.values"}}
                                    ]
                                }
                            },
                        }
                    },
                    "date_histogram": {
                        "extended_bounds": {
                            "max": filter_settings["end_date"],
                            "min": filter_settings["start_date"],
                        },
                        "field": "timestamp",
                        "interval": interval,
                        "min_doc_count": 0,
                    },
                }
            },
            "query": {
                "bool": {
                    "filter": [
                        {"terms": {"resource_id": [filter_settings["resource"][0]]}},
                        {
                            "range": {
                                "timestamp": {
                                    "gte": filter_settings["start_date"],
                                    "lte": filter_settings["end_date"],
                                }
                            }
                        },
                    ]
                }
            },
        }
        if group_id:
            parent_agg = es_query["aggs"]["parent_agg"]
            filters = parent_agg["aggs"]["types"]["filter"]["bool"]["filter"]
            filters.append({"terms": {"tags.group_id.values": [group_id]}})

        index_names = es_index_name_limiter(
            start_date=filter_settings["start_date"],
            end_date=filter_settings["end_date"],
            ixtypes=["reports"],
        )

        if not index_names:
            return []

        result = Datastores.es.search(
            body=es_query, index=index_names, doc_type="log", size=0
        )
        series = []
        for bucket in result["aggregations"]["parent_agg"]["buckets"]:
            point = {
                "x": datetime.utcfromtimestamp(int(bucket["key"]) / 1000),
                "report": 0,
                "not_found": 0,
                "slow_report": 0,
            }
            for subbucket in bucket["types"]["sub_agg"]["buckets"]:
                if subbucket["key"] == "slow":
                    point["slow_report"] = subbucket["doc_count"]
                elif subbucket["key"] == "error":
                    point["report"] = subbucket["doc_count"]
                elif subbucket["key"] == "not_found":
                    point["not_found"] = subbucket["doc_count"]
            series.append(point)
        return series
