# By: Riasat Ullah
# This class syncs up status pages stored in db with that in cache. In the case of retrievals, first attempt
# is made at the cache and then falls back to the db for cache misses.

from cache_queries import cache_status_pages
from dbqueries.status_pages import db_status_page_posts, db_status_page_previews, db_status_page_subscribers
from modules import status_page_notifier
from modules.alert_logger import AlertLogger
from modules.notice_allocator import NoticeAllocator
from taskcallrest import settings
from utils import app_notice, mail, times, var_names
import datetime


def refresh_status_page_details(conn, client, timestamp, page_ref_id):
    '''
    Refresh the details of a status page stored in cache with that from the database.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param page_ref_id: (concealed) reference ID of the page
    :errors: AssertionError, DatabaseError, Redis errors
    '''
    if settings.CACHE_ON:
        live_page_det = db_status_page_previews.get_status_page_current_status(
            conn, timestamp, is_published=True, page_ref_id=page_ref_id)
        live_page_det[var_names.last_refreshed] = timestamp
        if live_page_det is not None and live_page_det[var_names.is_published]:
            cache_status_pages.store_single_status_page(client, live_page_det)


def get_live_status_page(conn, client, timestamp, page_url=None, page_ref_id=None,
                         check_in_db=True, store_misses=True):
    '''
    Get the current live state of a status page.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param page_url: URL of the status page
    :param page_ref_id: (concealed) reference ID of the page
    :param check_in_db: if the database should be checked or not. (Since this query's source will be public facing,
                it might be desirable to avoid hitting the database)
    :param store_misses: (boolean) True if cache misses retrieved from db must be stored in cache; False otherwise
    :return: (dict) -> status page details
    :errors: AssertionError, DatabaseError, Redis errors
    '''
    retrieved_page = None

    if settings.CACHE_ON:
        retrieved_page = cache_status_pages.get_single_status_page(client, page_url, page_ref_id)

        # Refresh the cached status page with data from the db every hour
        if retrieved_page[var_names.last_refreshed] < timestamp - datetime.timedelta(hours=1):
            retrieved_page = None
            check_in_db = True

    if check_in_db and retrieved_page is None:
        retrieved_page = db_status_page_previews.get_status_page_current_status(
            conn, timestamp, is_published=True, page_url=page_url, page_ref_id=page_ref_id)

        if retrieved_page is not None and store_misses and settings.CACHE_ON:
            cache_status_pages.store_single_status_page(client, retrieved_page)

    return retrieved_page


def remove_status_page_details(client, page_ref_id):
    '''
    Remove the details of status page from cache.
    :param client: cache client
    :param page_ref_id: (concealed) reference ID of the status page
    :errors: Redis errors
    '''
    if settings.CACHE_ON:
        cache_status_pages.remove_single_status_page(client, page_ref_id)


def create_status_page_post(conn, client, timestamp, organization_id, page_ref_id, event_type, title, message, status,
                            page_impact, next_update_time, notify_subscribers, impacted_components, post_id=None,
                            maintenance_start=None, maintenance_end=None, auto_update=None, user_id=None,
                            org_inst_id=None):
    '''
    Create a new status page instance. The instance may actually not get created if it requires approval.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param page_ref_id: (concealed) reference ID of the status page
    :param event_type: type of event (INCIDENT, MAINTENANCE)
    :param title: title of the incident
    :param message: message of the incident
    :param status: status of the incident
    :param page_impact: the impact the incident will have on the page
    :param next_update_time: (integer) number of minutes after which an update will be made
    :param notify_subscribers: (boolean) should subscribers be notified or not
    :param impacted_components: (list) component types -> [{business reference id: , status: }, ...]
    :param post_id: ID of the post if it has already been created
    :param maintenance_start: time when the maintenance should start (for maintenance events)
    :param maintenance_end: time when the maintenance should end (for maintenance events)
    :param auto_update: (boolean) should the instance be updated automatically (for maintenance events)
    :param user_id: ID of the user creating the event (None if it is automatically created by the system)
    :param org_inst_id: ID of the organization instance ID this post is synced with
    :return: (tuple) -> post id, page id, requires approval or not, event_ref_id
    :errors: AssertionError, DatabaseError, LookupError, Redis errors
    '''
    pub_post_id, page_id, page_name, req_apv, evn_ref_id = db_status_page_posts.add_event(
        conn, timestamp, organization_id, page_ref_id, event_type, title, message, status, page_impact,
        next_update_time, notify_subscribers, impacted_components, post_id=post_id,
        maintenance_start=maintenance_start, maintenance_end=maintenance_end, auto_update=auto_update,
        user_id=user_id, org_inst_id=org_inst_id
    )
    if req_apv:
        apv_list = db_status_page_posts.list_status_page_approvers_to_notify(conn, timestamp, page_id=page_id)
        if len(apv_list) > 0:
            notifier = NoticeAllocator()
            notifier.handle_status_page_pending_post_dispatch(
                page_ref_id, page_name, title, message, event_type, evn_ref_id, apv_list
            )
            if len(notifier.email_messages) > 0:
                mail.AmazonSesBulkDispatcher(notifier.email_messages).start()
            if len(notifier.push_notices) > 0:
                app_notice.NoticeSender(notifier.push_notices).start()
            if len(notifier.alert_logs) > 0:
                AlertLogger(conn, notifier.alert_logs).start()

    if settings.CACHE_ON and pub_post_id is not None and not req_apv:
        refresh_status_page_details(conn, client, timestamp, page_ref_id)

        if notify_subscribers:
            cache_status_pages.store_pending_status_page_update(client, post_id)

    return pub_post_id, page_id, page_name, req_apv, evn_ref_id


def approve_status_page_event(conn, client, timestamp, organization_id, page_ref_id, event_ref_id, user_id):
    '''
    Approve a status page event.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param page_ref_id: (concealed) reference ID of the status page
    :param event_ref_id: (concealed) reference ID of the event
    :param user_id: (int) ID of the user approving the event
    :return: (tuple) -> post ID, type of event, if subscribers should be notified or not
    '''
    pub_post_id, evn_type, to_ntf_sub = db_status_page_posts.approve_event(
        conn, timestamp, organization_id, page_ref_id, event_ref_id, user_id
    )

    if settings.CACHE_ON and pub_post_id is not None:
        refresh_status_page_details(conn, client, timestamp, page_ref_id)

        if to_ntf_sub:
            cache_status_pages.store_pending_status_page_update(client, pub_post_id)

    return pub_post_id, evn_type, to_ntf_sub


def delete_post(conn, client, timestamp, organization_id, page_ref_id, post_id, user_id):
    '''
    Purge a status page instance.
    :param conn: db connection
    :param client: cache client
    :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 post_id: ID of the published post to redact
    :param user_id: ID of the user who is redacting the instance
    :errors: AssertionError, DatabaseError, LookupError
    '''
    db_status_page_posts.delete_post(conn, timestamp, organization_id, post_id, user_id)
    if settings.CACHE_ON:
        refresh_status_page_details(conn, client, timestamp, page_ref_id)


def send_status_page_subscriber_confirmation_request(conn, page_ref_id, sub_ref_list):
    '''
    Send a notice to status page subscribers asking for confirmation.
    :param conn: db connection
    :param page_ref_id: (concealed) reference ID of the status page
    :param sub_ref_list: (list) of subscriber details
    '''
    if page_ref_id is not None and len(sub_ref_list) > 0:
        page_name, page_url, page_lang, logo_url, notf_list = \
            db_status_page_subscribers.get_status_page_subscribers_info_for_confirmation(
                conn, times.get_current_timestamp(), page_ref_id, sub_ref_list)

        status_page_notifier.request_confirmation_from_subscribers(
            page_lang, notf_list, page_name, page_url, logo_url
        )
