# By: Riasat Ullah
# This file contains all database queries that are related to organization external SSO.

from exceptions.user_exceptions import InvalidRequest
from psycopg2 import errorcodes
from utils import constants, errors, key_manager, var_names
from validations import itsm_validator
import datetime
import json
import psycopg2


def create_external_sso(conn, timestamp, organization_id, sso_name, description, integration_type,
                        saml_certificate=None, saml_key=None, login_url=None, logout_url=None, metadata_url=None,
                        entity_id=None, vendor_id=None, vendor_subdomain=None, additional_info=None):
    '''
    Create a new external SSO configuration.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param sso_name: name of the SSO settings
    :param description: description of what the SSO setting is for
    :param integration_type: the type of integration
    :param saml_certificate: SAML certificate
    :param saml_key: SAML key
    :param login_url: login url to redirect to
    :param logout_url: logout url to redirect to
    :param metadata_url: SAML metadata url
    :param entity_id: SAML entity ID
    :param vendor_id: ID of the SSO vendor
    :param vendor_subdomain: the subdomain of the organization with the SSO vendor
    :param additional_info: any additional information that might be relevant
    :errors: AssertionError, DatabaseError, InvalidRequest
    '''
    assert isinstance(timestamp, datetime.datetime)
    itsm_validator.validate_external_sso_settings(
        organization_id, sso_name, description, integration_type, saml_certificate, saml_key, login_url, logout_url,
        metadata_url, entity_id, vendor_id, vendor_subdomain, additional_info
    )
    if additional_info is not None:
        additional_info = json.dumps(additional_info)

    query = '''
            select create_external_sso(
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s, %s
            );
            '''
    query_params = (organization_id, timestamp, constants.end_timestamp, key_manager.generate_reference_key(),
                    sso_name, description, integration_type, saml_certificate,
                    saml_key, login_url, logout_url, metadata_url,
                    entity_id, vendor_id, vendor_subdomain, additional_info,)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.CHECK_VIOLATION:
            raise InvalidRequest(errors.err_invalid_request)
    except psycopg2.DatabaseError:
        raise


def edit_external_sso(conn, timestamp, organization_id, sso_ref_id, sso_name, description, integration_type,
                      saml_certificate=None, saml_key=None, login_url=None, logout_url=None, metadata_url=None,
                      entity_id=None, vendor_id=None, vendor_subdomain=None, additional_info=None):
    '''
    Edit an existing external SSO configuration.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param sso_ref_id: concealed referenced ID of the external SSO configuration
    :param sso_name: name of the SSO settings
    :param description: description of what the SSO setting is for
    :param integration_type: the type of integration
    :param saml_certificate: SAML certificate
    :param saml_key: SAML key
    :param login_url: login url to redirect to
    :param logout_url: logout url to redirect to
    :param metadata_url: SAML metadata url
    :param entity_id: SAML entity ID
    :param vendor_id: ID of the SSO vendor
    :param vendor_subdomain: the subdomain of the organization with the SSO vendor
    :param additional_info: any additional information that might be relevant
    :errors: AssertionError, DatabaseError, InvalidRequest
    '''
    assert isinstance(timestamp, datetime.datetime)
    itsm_validator.validate_external_sso_settings(
        organization_id, sso_name, description, integration_type, saml_certificate, saml_key, login_url, logout_url,
        metadata_url, entity_id, vendor_id, vendor_subdomain, additional_info
    )
    unmasked_ref = key_manager.unmask_reference_key(sso_ref_id)
    if additional_info is not None:
        additional_info = json.dumps(additional_info)

    query = '''
            select edit_external_sso(
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s, %s,
                %s, %s, %s, %s
            );
            '''
    query_params = (organization_id, timestamp, constants.end_timestamp, unmasked_ref,
                    sso_name, description, integration_type, saml_certificate,
                    saml_key, login_url, logout_url, metadata_url,
                    entity_id, vendor_id, vendor_subdomain, additional_info,)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.CHECK_VIOLATION:
            raise InvalidRequest(errors.err_invalid_request)
    except psycopg2.DatabaseError:
        raise


def delete_external_sso(conn, timestamp, organization_id, sso_ref_id):
    '''
    Delete an external SSO settings.
    :param conn: db connection
    :param timestamp: timestamp when the request is being made
    :param organization_id: ID of the organization
    :param sso_ref_id: reference ID of the SSO settings
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_ref = key_manager.unmask_reference_key(sso_ref_id)

    query = '''
            update external_sso set end_timestamp = %(timestamp)s
            where start_timestamp <= %(timestamp)s
                and end_timestamp > %(timestamp)s
                and organization_id = %(org_id)s;
                and sso_ref_id = %(sso_ref)s;
            '''
    query_params = {'timestamp': timestamp, 'org_id': organization_id, 'sso_ref': unmasked_ref}
    try:
        conn.execute(query, query_params)
    except psycopg2.DatabaseError:
        raise


def get_external_sso_details(conn, timestamp, organization_id, sso_ref_id):
    '''
    Gets the external SSO details given the reference ID or the URL of the page it is imposed on.
    :param conn: db connection
    :param timestamp: timestamp when the request is being made
    :param organization_id: ID of the organization the external SSO configuration belongs to
    :param sso_ref_id: reference ID of the SSO configuration
    :return: (dict) of SSO details  |  None if no SSO configuration exists
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    unmasked_ref = key_manager.unmask_reference_key(sso_ref_id)

    query = '''
            select sso_name, intt.integration_type, saml_certificate, saml_key, login_url, logout_url, metadata_url,
                entity_id, vendor_id, vendor_subdomain, org_sso.additional_info
            from external_sso as ext
            join integration_types as intt using(integration_type_id)
            where intt.start_date <= %(timestamp)s
                and intt.end_date > %(timestamp)s
                and ext.start_timestamp <= %(timestamp)s
                and ext.end_timestamp > %(timestamp)s
                and ext.organization_id = %(org_id)s
                and ext.sso_ref_id = %(sso_ref)s;
            '''
    query_params = {'timestamp': timestamp, 'org_id': organization_id, 'sso_ref': unmasked_ref}
    try:
        result = conn.fetch(query, query_params)
        if len(result) > 0:
            for sso_name, intt_type, saml_cert, saml_key, in_url, out_url, meta_url, ent_id, vnd_id, \
                    vnd_sub, add_info in result:
                data = {
                    var_names.sso_name: sso_name,
                    var_names.sso_ref_id: sso_ref_id,
                    var_names.integration_type: intt_type,
                    var_names.saml_certificate: saml_cert,
                    var_names.saml_key: saml_key,
                    var_names.login_url: in_url,
                    var_names.logout_url: out_url,
                    var_names.metadata_url: meta_url,
                    var_names.entity_id: ent_id,
                    var_names.vendor_id: vnd_id,
                    var_names.vendor_subdomain: vnd_sub,
                    var_names.additional_info: add_info
                }
                return data
        else:
            return None
    except psycopg2.DatabaseError:
        raise


def list_external_sso(conn, timestamp, organization_id):
    '''
    Get the list of external SSO.
    :param conn: db connection
    :param timestamp: timestamp this request is being made on
    :param organization_id: ID of the organization
    :return: (list of list) -> [ [{sso_name: , sso_ref_id: , integration_type: }], ... ]
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)

    query = '''
            select sso_name, sso_ref_id, integration_type
            from external_sso as ext
            join integration_types as intt using(integration_type_id)
            where intt.start_date <= %(timestamp)s
                and intt.end_date > %(timestamp)s
                and ext.start_timestamp <= %(timestamp)s
                and ext.end_timestamp > %(timestamp)s
                and ext.organization_id = %(org_id)s
            order by sso_name;
            '''
    query_params = {'timestamp': timestamp, 'org_id': organization_id}
    try:
        result = conn.fetch(query, query_params)
        data = []
        for name_, ref_, int_typ_ in result:
            data.append({
                var_names.sso_name: name_,
                var_names.sso_ref_id: key_manager.conceal_reference_key(ref_),
                var_names.integration_type: int_typ_
            })
        return data
    except psycopg2.DatabaseError:
        raise


def get_basic_external_sso_list(conn, timestamp, organization_id):
    '''
    Get the basic external SSO list.
    :param conn: db connection
    :param timestamp: timestamp this request is being made on
    :param organization_id: ID of the organization
    :return: (list of list) -> [ [sso name, sso ref id], ... ]
    :errors: AssertionError, DatabaseError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)

    query = '''
            select sso_name, sso_ref_id
            from external_sso
            where start_timestamp <= %s
                and end_timestamp > %s
                and organization_id = %s
            order by sso_name;
            '''
    query_params = (timestamp, timestamp, organization_id,)
    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
