#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import annotations

import json
from datetime import datetime
from io import StringIO
from typing import Any
from typing import Dict
from typing import List
from urllib.parse import quote

import boto3
import pandas
from aws_xray_sdk.core import xray_recorder as xray
from jinja2 import Template
from numpy import datetime64

from iobot.models import IOBotList
from iobot.models import IOBotObject
from fivepillars.templates.vpd import VPD_HTML_TEMPLATE, VPD_TXT_TEMPLATE

class VPDRecord(IOBotObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {
            't_appid': 'external_select',
            'account': 'plain_text_input',
            't_environment': 'external_select',
            'vpd': 'plain_text_input',
            'report_date': 'datepicker'
        }


class VPDRecords(IOBotList):
    @xray.capture()
    def __init__(self, records: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(records, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> records = VPDRecords([VPDRecord(**{'t_appid':'SVC02985', 'account':'747207162522','t_environment':'PRD', 'vpd':0.1, 'report_date':'2021-02-20'}),VPDRecord(**{'t_appid':'SVC02109', 'account':'715066653912','t_environment':'PRD', 'vpd':18.0, 'report_date':'2021-02-14'})])

        >>> data = json.loads(records.to_json())

        >>> data['records'][0]['t_appid']
        'SVC02985'
        """
        return super().to_json(type_name='records')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> records = VPDRecords([VPDRecord(**{'t_appid':'SVC02985', 'account':'747207162522','t_environment':'PRD', 'vpd':0.1, 'report_date':'2021-02-20'}),VPDRecord(**{'t_appid':'SVC02109', 'account':'715066653912','t_environment':'PRD', 'vpd':18.0, 'report_date':'2021-02-14'})])

        >>> data = records.to_csv().lstrip().splitlines()

        >>> len(data)
        3
        """
        return super().to_csv()

    @staticmethod
    def to_dataframe(report_file: str = "", report_data: list = None) -> pandas.DataFrame:
        """
        >>> vpd_report = VPDRecords.to_dataframe('tests/data/vpd_index_report.csv')
        >>> vpd_report
            t_appid  start_num_instances  ...  end_vpd vpd_change
        0       NaN                  4.0  ...    26.33      17.02
        1    A00274                 31.0  ...    24.03       7.95
        2    A00905                  4.0  ...    38.25      12.50
        3    A06228                  6.0  ...    32.00      10.34
        4    A08099                  1.0  ...    48.00     -17.24
        ..      ...                  ...  ...      ...        ...
        274     NaN                  5.0  ...      NaN        NaN
        275     NaN                 18.0  ...      NaN        NaN
        276     NaN                  8.0  ...      NaN        NaN
        277     NaN                168.0  ...      NaN        NaN
        278     NaN                  1.0  ...      NaN        NaN
        <BLANKLINE>
        [279 rows x 8 columns]

        >>> vpd_report = VPDRecords.to_dataframe(report_data=['"t_appid","start_num_instances","end_num_instances","start_date","end_date","start_vpd","end_vpd","vpd_change"', '"SVC00765","108","107","2021-01-26","2021-02-25","24.72","28.76","16.34"'])

        >>> vpd_report
            t_appid  start_num_instances  ...  end_vpd vpd_change
        0  SVC00765                  108  ...    28.76      16.34
        <BLANKLINE>
        [1 rows x 8 columns]
        """
        if report_file:
            with open(report_file, 'r') as report:

                df = pandas.read_csv(StringIO(report.read()),
                                     dtype={'account': str})

                return df.astype({'start_date': datetime64,
                                  'end_date': datetime64,
                                  'start_vpd': float,
                                  'end_vpd': float,
                                  'vpd_change': float})

        if report_data:
            df = pandas.read_csv(StringIO('\n'.join(report_data)),
                                 dtype={'account': str})

            return df.astype({'start_date': datetime64,
                              'end_date': datetime64,
                              'start_vpd': float,
                              'end_vpd': float,
                              'vpd_change': float})

    @staticmethod
    def get_vpd(t_appids: List[str] = None,
                records: pandas.DataFrame = None) -> pandas.DataFrame:
        """
        >>> data = VPDRecords.get_vpd(t_appids=['SVC02109'],
        ...                           records=VPDRecords.to_dataframe(
        ...                             'tests/data/vpd_index_report.csv'))
        ...
        >>> data
              t_appid  start_num_instances  ...  end_vpd vpd_change
        146  SVC02109                 21.0  ...    35.06      28.28
        <BLANKLINE>
        [1 rows x 8 columns]

        >>> from psi_athena_api import Qualys

        >>> t = Qualys(log,boto_session,config_file='environments/unit.ini')

        >>> query_id = t.start_vpd_trends_query_execution(parameters={'ids':['SVC02221']})

        >>> from time import sleep

        >>> attempts: int = 0

        >>> status: str = ""

        >>> while status not in ['SUCCEEDED', 'FAILED', 'CANCELLED']:
        ...     if attempts > 20:
        ...         status = 'FAILED'
        ...         break
        ...     sleep(1)
        ...     status = t.query_execution_status(query_id)
        ...     attempts += 1

        >>> records = t.get_query_results(query_id)

        >>> data = VPDRecords.get_vpd(records=VPDRecords.to_dataframe(report_data=records))

        >>> data
            t_appid  start_num_instances  ...  end_vpd vpd_change
        0  SVC02221                    3  ...     4.33        0.0
        <BLANKLINE>
        [1 rows x 8 columns]

        """

        if t_appids:
            filter_mask = records['t_appid'].str.upper().isin(t_appids)
            records = records[filter_mask]

        return records

    def to_report_emails(self, services: dict = None, format: str = 'text/plain') -> List[dict]:
        """
        >>> data = VPDRecords.get_vpd(t_appids=['SVC00765'],
        ...                           records=VPDRecords.to_dataframe(
        ...                             'tests/data/vpd_index_report.csv'))

        >>> records: VPDRecords = VPDRecords.to_vpd_records(data=data)

        >>> messages: List[str]

        >>> services: dict = json.loads('{"SVC00765":{"t_appid":"SVC01188","id":"P65E452","name":"Pearson Access","teams":[{"id":"PS54O87","type":"team_reference","summary":"Cloud Delivery Engineering - Clinical"}],"sre":[{"name":"Mike Feineman","email":"mike.feinenam@pearson.com","job_title":"Manager, Site Reliability","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP77J","type":"user","summary":"Mike Feineman"},{"name":"Ryan Olsen","email":"ryan.olsen@pearson.com","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P28P130","type":"user","summary":"Ryan Olsen"},{"name":"John Torres","email":"john.torres@pearson.com","job_title":null,"teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PQ7SXBW","type":"user","summary":"John Torres"},{"name":"Stephen Davidson","email":"stephen.davidson@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Stephen Davidson"},{"name":"Craig McNiel","email":"craig.mcniel@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Craig McNiel"},{"name":"Optimus Prime","email":"aidevops@pearson.com","job_title":"Automated, Bot Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP778","type":"user","summary":"Optimus Prime"}]},"SVC02000":{"t_appid":"SVC02000","id":"P65E452","name":"Continuous Flow","teams":[{"id":"PS54O87","type":"team_reference","summary":"Cloud Delivery Engineering - Clinical"}],"sre":[{"name":"Mike Feineman","email":"mike.feinenam@pearson.com","job_title":"Manager, Site Reliability","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP77J","type":"user","summary":"Mike Feineman"},{"name":"Ryan Olsen","email":"ryan.olsen@pearson.com","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P28P130","type":"user","summary":"Ryan Olsen"},{"name":"John Torres","email":"john.torres@pearson.com","job_title":null,"teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PQ7SXBW","type":"user","summary":"John Torres"},{"name":"Stephen Davidson","email":"stephen.davidson@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Stephen Davidson"},{"name":"Craig McNiel","email":"craig.mcniel@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Craig McNiel"},{"name":"Optimus Prime","email":"aidevops@pearson.com","job_title":"Automated, Bot Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP778","type":"user","summary":"Optimus Prime"}]},"SVC01379":{"t_appid":"SVC01379","id":"P65E452","name":"TestNav 8","teams":[{"id":"PS54O87","type":"team_reference","summary":"Cloud Delivery Engineering - Clinical"}],"sre":[{"name":"Mike Feineman","email":"mike.feinenam@pearson.com","job_title":"Manager, Site Reliability","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP77J","type":"user","summary":"Mike Feineman"},{"name":"Ryan Olsen","email":"ryan.olsen@pearson.com","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P28P130","type":"user","summary":"Ryan Olsen"},{"name":"John Torres","email":"john.torres@pearson.com","job_title":null,"teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PQ7SXBW","type":"user","summary":"John Torres"},{"name":"Stephen Davidson","email":"stephen.davidson@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Stephen Davidson"},{"name":"Craig McNiel","email":"craig.mcniel@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Craig McNiel"},{"name":"Optimus Prime","email":"aidevops@pearson.com","job_title":"Automated, Bot Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP778","type":"user","summary":"Optimus Prime"}]},"SVC01990":{"t_appid":"SVC01990","id":"P65E452","name":"ePEN 2 US","teams":[{"id":"PS54O87","type":"team_reference","summary":"Cloud Delivery Engineering - Clinical"}],"sre":[{"name":"Mike Feineman","email":"mike.feinenam@pearson.com","job_title":"Manager, Site Reliability","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP77J","type":"user","summary":"Mike Feineman"},{"name":"Ryan Olsen","email":"ryan.olsen@pearson.com","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P28P130","type":"user","summary":"Ryan Olsen"},{"name":"John Torres","email":"john.torres@pearson.com","job_title":null,"teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PQ7SXBW","type":"user","summary":"John Torres"},{"name":"Stephen Davidson","email":"stephen.davidson@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Stephen Davidson"},{"name":"Craig McNiel","email":"craig.mcniel@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Craig McNiel"},{"name":"Optimus Prime","email":"aidevops@pearson.com","job_title":"Automated, Bot Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP778","type":"user","summary":"Optimus Prime"}]},"SVC00966":{"t_appid":"SVC00966","id":"P65E452","name":"SchoolNet","teams":[{"id":"PS54O87","type":"team_reference","summary":"Cloud Delivery Engineering - Clinical"}],"sre":[{"name":"Mike Feineman","email":"mike.feinenam@pearson.com","job_title":"Manager, Site Reliability","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP77J","type":"user","summary":"Mike Feineman"},{"name":"Ryan Olsen","email":"ryan.olsen@pearson.com","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P28P130","type":"user","summary":"Ryan Olsen"},{"name":"John Torres","email":"john.torres@pearson.com","job_title":null,"teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PQ7SXBW","type":"user","summary":"John Torres"},{"name":"Stephen Davidson","email":"stephen.davidson@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Stephen Davidson"},{"name":"Craig McNiel","email":"craig.mcniel@pearson.com","job_title":"DevOps Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"P8L1QAF","type":"user","summary":"Craig McNiel"},{"name":"Optimus Prime","email":"aidevops@pearson.com","job_title":"Automated, Bot Engineer","teams":[{"id":"PRJ900S","type":"team_reference","summary":"SRE Team 1"}],"id":"PKCP778","type":"user","summary":"Optimus Prime"}]}}')  # noqa: E501

        >>> messages = records.to_report_emails(services=services)

        >>> len(messages)
        5

        >>> messages[0].get('body').startswith('Hello Mike,')
        True

        >>> messages = records.to_report_emails(services=services, format='text/html')

        >>> messages[0].get('html_body').startswith('<div>')
        True

        >>> 'Mike' in messages[0].get('html_body')
        True
        """

        template_plain: Template = VPD_TXT_TEMPLATE

        template_html: Template = VPD_HTML_TEMPLATE

        teams_for_service: Dict[str, List[str]] = {}
        messages: list = []
        # build a consolidated list of teams and their tappids
        for record in self.data:
            t_appid: str = record.get('t_appid')
            service: dict = services.get(t_appid)

            for team in service.get('teams'):
                if team.get('id') in teams_for_service:
                    teams_for_service[team.get('id')].append(t_appid)
                else:
                    teams_for_service[team.get('id')] = [t_appid]

        for team_id in teams_for_service.keys():
            service_ids: list = list(set(teams_for_service.get(team_id)))
            records_for_team: VPDRecords = VPDRecords([])

            for record in self.data:
                t_appid: str = record.get('t_appid')
                service: dict = services.get(t_appid)

                if record.get('t_appid') in service_ids:
                    records_for_team.append(record)

            for service_id in teams_for_service.get(team_id):
                service: dict = services.get(service_id)
                team_name: str = ""
                for svc_team in service.get('teams'):
                    if svc_team.get('id') == team_id:
                        team_name = svc_team.get('summary')
                        scorecard_url = f'https://pearson.grafana.net/d/55buniVnk/well-architected-practices-beta?orgId=1&var-team={quote(team_name)}&var-t_appid=All'

                start_date: str = records_for_team[0].get('start_date').date()
                end_date: str = records_for_team[0].get('end_date').date()

                for person in service['sre']:
                    if person.get('email') == 'aidevops@pearson.com':
                        continue  # skip adding this distribution list to avoid generating duplicate emails

                    message: dict = {
                        'name': person.get('name'),
                        'email': person.get('email'),
                        'start_date': start_date,
                        'end_date': end_date,
                        'team_name': team_name,
                        'scorecard_url': scorecard_url,
                        'records': records_for_team,
                        'services': services
                    }
                    # print(f'prepared message: {message}')
                    rendered_message_envelop: dict = {
                      'to': [person.get('email')],
                      'subject': f'FivePillars VPD Report {start_date} - {end_date} for {team_name}',  # noqa: E501
                      'body': template_plain.render(**message)
                    }
                    if format == 'text/html':
                        rendered_message_envelop['html_body'] = template_html.render(
                            **message)

                    messages.append(rendered_message_envelop)

        deduped_reports = [i for n, i in enumerate(
            messages) if i not in messages[n + 1:]]
        return deduped_reports

    @staticmethod
    def to_vpd_records(data: pandas.DataFrame = None,
                       group_by: List[str] = None) -> VPDRecords:
        """
        >>> data = VPDRecords.get_vpd(t_appids=['SVC02109'],
        ...                           records=VPDRecords.to_dataframe(
        ...                             'tests/data/vpd_index_report.csv'))

        >>> records: VPDRecords = VPDRecords.to_vpd_records(data=data)

        >>> records
        [{'t_appid': 'SVC02109', 'start_num_instances': 21.0, 'end_num_instances': 17.0, 'start_date': Timestamp('2021-01-26 00:00:00'), 'end_date': Timestamp('2021-02-25 00:00:00'), 'start_vpd': 27.33, 'end_vpd': 35.06, 'vpd_change': 28.28}]

        >>> type(records)
        <class 'models.VPDRecords'>

        >>> len(records)
        1

        >>> records.to_json()
        '{\\n  "records": [\\n    {\\n      "end_date": {},\\n      "end_num_instances": 17.0,\\n      "end_vpd": 35.06,\\n      "start_date": {},\\n      "start_num_instances": 21.0,\\n      "start_vpd": 27.33,\\n      "t_appid": "SVC02109",\\n      "vpd_change": 28.28\\n    }\\n  ]\\n}'
        """
        return VPDRecords((
            VPDRecord(**record[1].to_dict()) for record in data.iterrows()))

    @staticmethod
    def push_vpd_as_graphite_metric(data: pandas.DataFrame):
        """
        >>> skip('moved out of current work scope - possibly deprecated')

        >>> data = VPDRecords.get_vpd(t_appids=['SVC02109'],
        ...                           records=VPDRecords.to_dataframe(
        ...                             'tests/data/vpd_index_report.csv'))
        >>> response = VPDRecords.push_vpd_as_graphite_metric(data=data)
        """
        pass

    @staticmethod
    def push_vpd_as_cloudwatch_metric(boto_session: boto3.session.Session = None, data: Any = None, namespace='FivePillars/VPD/Summary'):
        """
        >>> skip('moved out of current work scope - possibly deprecated')

        >>> data = VPDRecords.get_vpd(records=VPDRecords.to_dataframe(
        ...                             'tests/data/vpd_index_report.csv'))

        >>> response = VPDRecords.push_vpd_as_cloudwatch_metric(boto_session=boto_session,data=data)

        """
        client = boto_session.client('cloudwatch')
        if type(data) == pandas.core.frame.DataFrame:
            for datum in data.iterrows():
                metric_data: list = [
                    {
                        'MetricName': datum[0],
                        'Value':  datum[1].get('vpd').get('mean'),
                        'Timestamp': datetime.now(),
                    },
                ]

                response = client.put_metric_data(
                    Namespace=namespace,
                    MetricData=metric_data
                )
                return response

    @staticmethod
    def get_cloudwatch_metric_graph_as_png(boto_session: boto3.session.Session = None, namespace='FivePillars/VPD/Summary', metric_names: List[str] = None):
        """
        >>> response = VPDRecords.get_cloudwatch_metric_graph_as_png(boto_session=boto_session, metric_names=['SVC02109','SVC03013','SVC00618','SVC02237','SVC01379'])

        """
        client = boto_session.client('cloudwatch')
        metric_widget: dict = {"metrics": []}

        for id in metric_names:
            metric_widget['metrics'].append([namespace, id])

        response = client.get_metric_widget_image(
            MetricWidget=json.dumps(metric_widget),
            OutputFormat='png'
        )

        with open('widget.png', 'wb') as image:
            data = response['MetricWidgetImage']
            image.write(data)
