# By: Riasat Ullah
# This file contains database queries for status page templates.

from dbqueries.status_pages import db_status_pages
from psycopg2 import errorcodes
from utils import constants, errors, key_manager, var_names
from validations import string_validator
import configuration as configs
import datetime
import json
import psycopg2
import uuid


def create_status_page_template(conn, timestamp, organization_id, page_ref_id, template_type, template_name,
                                description, title, message, status, page_impact, next_update_mts, notify_subscribers,
                                template_components):
    '''
    Create a new status page template.
    :param conn: db connection
    :param timestamp: timestamp this request is being made
    :param organization_id: ID of the organization
    :param page_ref_id: reference ID of the page
    :param template_type: the type of template (INCIDENT, MAINTENANCE)
    :param template_name: easily identifiable name of the template
    :param description: short description of what the template is for
    :param title: title of the incident/maintenance
    :param message: message of the incident/maintenance
    :param status: status of the incident
    :param page_impact: the impact the incident will have on the page
    :param next_update_mts: (integer) number of minutes after which an update will be made
    :param notify_subscribers: (boolean) should subscribers be notified or not
    :param template_components: (list of dict) of template components
    :errors: AssertionError, DatabaseError, LookupError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_pg_ref = key_manager.unmask_reference_key(page_ref_id)
    new_components = validate_and_internalize_status_page_template_data(
        conn, timestamp, organization_id, page_ref_id, template_type, template_name, description, title,
        message, status, page_impact, next_update_mts, notify_subscribers, template_components
    )
    query = '''
            select create_status_page_template(
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s
            );
            '''
    query_params = (organization_id, timestamp, constants.end_timestamp, unmasked_pg_ref,
                    key_manager.generate_reference_key(), template_type, template_name, description,
                    title, message, status, page_impact,
                    next_update_mts, notify_subscribers, json.dumps(new_components),)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.CHECK_VIOLATION:
            raise LookupError(errors.err_unknown_resource)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def edit_status_page_template(conn, timestamp, organization_id, page_ref_id, template_ref_id, template_type,
                              template_name, description, title, message, status, page_impact, next_update_mts,
                              notify_subscribers, template_components):
    '''
    Edit an existing status page template.
    :param conn: db connection
    :param timestamp: timestamp this request is being made
    :param organization_id: ID of the organization
    :param page_ref_id: (concealed) reference ID of the page
    :param template_ref_id: reference ID of the template
    :param template_type: the type of template (INCIDENT, MAINTENANCE)
    :param template_name: easily identifiable name of the template
    :param description: short description of what the template is for
    :param title: title of the incident/maintenance
    :param message: message of the incident/maintenance
    :param status: status of the incident
    :param page_impact: the impact the incident will have on the page
    :param next_update_mts: (integer) number of minutes after which an update will be made
    :param notify_subscribers: (boolean) should subscribers be notified or not
    :param template_components: (list of dict) of template components
    :errors: AssertionError, DatabaseError, LookupError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_pg_ref = key_manager.unmask_reference_key(page_ref_id)
    unmasked_tmpl_ref = key_manager.unmask_reference_key(template_ref_id)
    new_components = validate_and_internalize_status_page_template_data(
        conn, timestamp, organization_id, page_ref_id, template_type, template_name, description, title, message,
        status, page_impact, next_update_mts, notify_subscribers, template_components
    )
    query = '''
            select edit_status_page_template(
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s
            );
            '''
    query_params = (organization_id, timestamp, constants.end_timestamp, unmasked_pg_ref,
                    unmasked_tmpl_ref, template_type, template_name, description,
                    title, message, status, page_impact,
                    next_update_mts, notify_subscribers, json.dumps(new_components),)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.CHECK_VIOLATION:
            raise LookupError(errors.err_unknown_resource)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def delete_status_page_template(conn, timestamp, organization_id, page_ref_id, template_ref_id):
    '''
    Delete status page templates.
    :param conn: db connection
    :param timestamp: timestamp this request is being made
    :param organization_id: ID of the organization the template belongs to
    :param page_ref_id: (concealed) reference ID of the page
    :param template_ref_id: (concealed) reference ID of the template
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_pg_ref = key_manager.unmask_reference_key(page_ref_id)
    unmasked_tmpl_ref = key_manager.unmask_reference_key(template_ref_id)

    query = "select delete_status_page_template(%s, %s, %s, %s);"
    query_params = (organization_id, timestamp, unmasked_pg_ref, unmasked_tmpl_ref,)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.CHECK_VIOLATION:
            raise LookupError(errors.err_unknown_resource)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def get_status_page_template_details(conn, timestamp, organization_id, page_ref_id, template_ref_id):
    '''
    Get the details of a status page template.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization the template belongs to
    :param page_ref_id: reference ID of the status page the template belongs to
    :param template_ref_id: reference ID of the template
    :return: (dict) of template details
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_pg_ref = key_manager.unmask_reference_key(page_ref_id)
    unmasked_tmpl_ref = key_manager.unmask_reference_key(template_ref_id)

    query = '''
            with t1 as (
                select tmpl.template_id, sp.page_ref_id, template_ref_id, template_type, template_name,
                    tmpl.description, title, message, status, page_impact, next_update_minutes, notify_subscribers
                from status_page_templates as tmpl
                join status_pages as sp using(page_id)
                where tmpl.start_timestamp <= %(timestamp)s
                    and tmpl.end_timestamp > %(timestamp)s
                    and tmpl.template_ref_id = %(tmpl_ref)s
                    and sp.start_timestamp <= %(timestamp)s
                    and sp.end_timestamp > %(timestamp)s
                    and sp.organization_id = %(org_id)s
                    and sp.page_ref_id = %(pg_ref)s
            )
            , t2 as (
                select template_id, json_agg(json_build_object(
                    'service_name', bus.service_name,
                    'business_service_ref_id', bus.business_service_ref_id,
                    'status', comp.status
                )) as tmpl_comps
                from status_page_template_components as comp
                join business_services as bus
                    on bus.business_serviceid = comp.business_serviceid
                        and bus.organization_id = %(org_id)s
                        and bus.start_timestamp <= %(timestamp)s
                        and bus.end_timestamp > %(timestamp)s
                where comp.start_timestamp <= %(timestamp)s
                    and comp.end_timestamp > %(timestamp)s
                    and template_id in (select template_id from t1)
                group by template_id
            )
            select template_ref_id, page_ref_id, template_type, template_name, description, title,
                message, status, page_impact, next_update_minutes, notify_subscribers, t2.tmpl_comps
            from t1
            join t2 using(template_id);
            '''
    query_params = {'timestamp': timestamp, 'org_id': organization_id, 'pg_ref': unmasked_pg_ref,
                    'tmpl_ref': unmasked_tmpl_ref}
    try:
        result = conn.fetch(query, query_params)
        if len(result) == 0:
            raise LookupError(errors.err_unknown_resource)
        for tmpl_ref_, pg_ref_, tmpl_type_, tmpl_name_, desc_, ttl_, msg_, sts_, imp_, nxt_upd_, ntf_, comp_ in result:

            for item in comp_:
                item[var_names.business_service_ref_id] = key_manager.conceal_reference_key(
                    uuid.UUID(item[var_names.business_service_ref_id]))

            data = {
                var_names.template_ref_id: key_manager.conceal_reference_key(tmpl_ref_),
                var_names.page_ref_id: pg_ref_,
                var_names.template_type: tmpl_type_,
                var_names.template_name: tmpl_name_,
                var_names.description: desc_,
                var_names.title: ttl_,
                var_names.message: msg_,
                var_names.status: sts_,
                var_names.page_impact: imp_,
                var_names.next_update: nxt_upd_,
                var_names.notify_subscribers: ntf_,
                var_names.impacted_business_services: comp_
            }
            return data
    except psycopg2.DatabaseError:
        raise


def list_status_page_templates(conn, timestamp, organization_id, page_ref_id, row_limit=None, row_offset=None):
    '''
    Get the list of all templates associated with a status page.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization the status page belongs to
    :param page_ref_id: reference ID of the status page.
    :param row_limit: (optional) number of templates to fetch
    :param row_offset: (optional) number of templates to skip ahead
    :return: (list of dict) of basic template details
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_pg_ref = key_manager.unmask_reference_key(page_ref_id)

    limit_cond = ''
    if row_limit is not None:
        assert isinstance(row_limit, int)
        limit_cond += ' limit {0} '.format(str(row_limit))
    if row_offset is not None:
        assert isinstance(row_offset, int)
        limit_cond += ' offset {0} '.format(str(row_offset))

    query = '''
            with t1 as (
                select template_id, template_ref_id, template_type, template_name, description, status, page_impact
                from status_page_templates
                where start_timestamp <= %(timestamp)s
                    and end_timestamp > %(timestamp)s
                    and page_id in (
                        select page_id from status_pages
                        where start_timestamp <= %(timestamp)s
                            and end_timestamp > %(timestamp)s
                            and organization_id = %(org_id)s
                            and page_ref_id = %(pg_ref)s
                    )
                    {0}
            )
            , t2 as (
                select template_id, json_agg(json_build_object(
                    'service_name', bus.service_name,
                    'business_service_ref_id', bus.business_service_ref_id
                )) as tmpl_comps
                from status_page_template_components as comp
                join business_services as bus
                    on bus.business_serviceid = comp.business_serviceid
                        and bus.organization_id = %(org_id)s
                        and bus.start_timestamp <= %(timestamp)s
                        and bus.end_timestamp > %(timestamp)s
                where comp.start_timestamp <= %(timestamp)s
                    and comp.end_timestamp > %(timestamp)s
                    and comp.template_id in (select template_id from t1)
                group by template_id
            )
            select template_ref_id, template_type, template_name, description, status, page_impact, tmpl_comps
            from t1
            join t2 using(template_id)
            order by template_name;
            '''.format(limit_cond)
    query_params = {'timestamp': timestamp, 'org_id': organization_id, 'pg_ref': unmasked_pg_ref}
    try:
        result = conn.fetch(query, query_params)
        data = []
        for tmpl_ref_, tmpl_type_, tmpl_name_, desc_, sts_, imp_, comps_ in result:
            data.append({
                var_names.template_ref_id: key_manager.conceal_reference_key(tmpl_ref_),
                var_names.template_type: tmpl_type_,
                var_names.template_name: tmpl_name_,
                var_names.description: desc_,
                var_names.status: sts_,
                var_names.page_impact: imp_,
                var_names.impacted_business_services: comps_
            })
        return data
    except psycopg2.DatabaseError:
        raise


def get_basic_status_page_templates_list(conn, timestamp, organization_id, page_ref_id):
    '''
    Get the basic status page templates list.
    :param conn: db connection
    :param timestamp: timestamp this request is being made on
    :param organization_id: ID of the organization
    :param page_ref_id: reference ID of the status page whose templates should be fetched
    :return: (list of list) -> [ [status page name, status page ref id], ... ]
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_pg_ref = key_manager.unmask_reference_key(page_ref_id)

    query = '''
            select template_name, template_ref_id
            from status_page_templates
            where start_timestamp <= %(timestamp)s
                and end_timestamp > %(timestamp)s
                and page_id in (
                    select page_id from status_pages
                    where start_timestamp <= %(timestamp)s
                        and end_timestamp > %(timestamp)s
                        and organization_id = %(org_id)s
                        and page_ref_id = %(pg_ref)s
                )
            order by template_name;
            '''
    query_params = {'timestamp': timestamp, 'org_id': organization_id, 'pg_ref': unmasked_pg_ref}
    try:
        result = conn.fetch(query, query_params)
        data = []
        for name_, key_ in result:
            data.append([name_, key_manager.conceal_reference_key(key_)])
        return data
    except psycopg2.DatabaseError:
        raise


def validate_and_internalize_status_page_template_data(
        conn, timestamp, organization_id, page_ref_id, template_type, template_name, description, title, message,
        status, page_impact, next_update_mts, notify_subscribers, template_components
):
    '''
    Validate the data of a status page template.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param page_ref_id: reference ID of the status page
    :param template_type: the type of template (INCIDENT, MAINTENANCE)
    :param template_name: easily identifiable name of the template
    :param description: short description of what the template is for
    :param title: title of the incident/maintenance
    :param message: message of the incident/maintenance
    :param status: status of the incident
    :param page_impact: the impact the incident will have on the page
    :param next_update_mts: (integer) number of minutes after which an update will be made
    :param notify_subscribers: (boolean) should subscribers be notified or not
    :param template_components: (optional) (list) of template component types
    '''
    new_components = []

    assert template_type in [constants.incident, constants.maintenance]
    assert string_validator.is_standard_name(template_name)
    if description is not None:
        assert isinstance(description, str)
    assert isinstance(title, str)
    if message is not None:
        assert isinstance(message, str)
    if template_type == constants.maintenance:
        assert status in configs.status_page_maintenance_statuses
        assert page_impact is None
    else:
        assert status in configs.status_page_incident_statuses
        assert page_impact in configs.status_page_impact_types

    if next_update_mts is not None:
        assert isinstance(next_update_mts, int)
    if notify_subscribers is not None:
        assert isinstance(notify_subscribers, bool)

    if template_components is not None:
        assert isinstance(template_components, list) and len(template_components) > 0
        page_bus_dict = db_status_pages.get_status_page_business_services_map(
            conn, timestamp, organization_id, page_ref_id)

        for comp in template_components:
            assert isinstance(comp, dict)
            assert len(comp) == 2
            unm_bus_ref = key_manager.unmask_reference_key(comp[var_names.business_service_ref_id])
            comp_sts = comp[var_names.status]
            if template_type == constants.maintenance:
                assert comp_sts is None
            else:
                assert comp_sts in configs.status_page_business_service_statuses

            new_components.append({
                var_names.business_service_id: page_bus_dict[unm_bus_ref],
                var_names.status: comp_sts
            })

        assert len(new_components) == len(template_components)

    return new_components
