from copy import deepcopy


import SpectrumCommon.Const.GB.GBConst as gbc

import SpectrumCommon.Const.PC.PCTags as pcmvt
import SpectrumCommon.Const.PC.PCModVarFields as pcmvf
import SpectrumCommon.Modvars.PC.PCReadDatabases as pcrd
import SpectrumCommon.Const.PC.PCDatabaseKeys as pcdbk
import SpectrumCommon.Modvars.PC.PCModVarUtil as pcmvu

# Notes:
#
#   1. You cannot base logic on whether a current MV tag exists in the code below.  It will always exist, because before this gets called, all missing MVs
#      will get added to the projection.
#
#   2. To trigger this, create a projection, say on the client, save, and grab it's projection ID.  Call GetModvars to get the library ID and put that into the appropriate
#      environment variable. Then call OpenProjection. Make sure to leave the projection on the client open while doing this because the proj ID changes when you open/close.
def PC_UpdateModvars(projection, created_projection):

    if PC_COST_INPUT_RECORDS_TAG_V1 in projection:
        _convert_cost_input_records_1(projection, PC_COST_INPUT_RECORDS_TAG_V1)
        _convert_cost_input_records_2(projection, pcmvt.PC_COST_INPUT_RECORDS_TAG)

    elif PC_COST_INPUT_RECORDS_TAG_V2 in projection:
        _convert_cost_input_records_2(projection, PC_COST_INPUT_RECORDS_TAG_V2)

    if PC_COSTING_SECT_RECORDS_TAG_V1 in projection:
        _convert_costing_sect_records_1(projection, PC_COSTING_SECT_RECORDS_TAG_V1)

    if PC_ACT_RECORDS_TAG_V1 in projection:
        _convert_act_records_1(projection, PC_ACT_RECORDS_TAG_V1)

    # A bug was discovered which caused activity records and line item records not to be removed when corresponding costing sections and subsections were deleted.
    # This function corrects this error by removing activities and line items which do not have a matching costing section or subsection.
    _remove_deleted_activities_and_line_items(projection)

    # delete tags
    for tag in PC_TagsForDeletion:
        if tag in projection:
            del projection[tag]

    return projection

PC_COST_INPUT_RECORDS_TAG_V1    = '<PC_CostInputRecords_V1>'
PC_COST_INPUT_RECORDS_TAG_V2    = '<PC_CostInputRecords_V2>'
PC_COSTING_SECT_RECORDS_TAG_V1  = '<PC_CostingSectionRecords_V1>'
PC_ACT_RECORDS_TAG_V1           = '<PC_ActivityRecords_V1>'

PC_TagsForDeletion = [
    PC_COST_INPUT_RECORDS_TAG_V1,
    PC_COST_INPUT_RECORDS_TAG_V2,
    PC_COSTING_SECT_RECORDS_TAG_V1,
    PC_ACT_RECORDS_TAG_V1,
]

def _reload_string_constants_cost_inputs(cost_input_records_value):
    cost_input_DB = pcrd.PC_load_cost_input_DB()

    # Create a dict out of the database for fast lookups. Key-value pairs are input ID - inner dicts where the inner dicts are
    # cost input string constant IDs and unit of measure string constant IDs
    cost_input_DB_dict = {}

    for cost_input_DB_obj in cost_input_DB:
        cost_input_DB_dict[str(cost_input_DB_obj[pcdbk.PC_COST_INPUT_ID_KEY_CIDB])] = {
            'costInputStringConstant'     : cost_input_DB_obj[pcdbk.PC_COST_INPUT_STR_CONST_KEY_CIDB],
            'unitOfMeasureStringConstant' : cost_input_DB_obj[pcdbk.PC_UNIT_OF_MEASURE_STR_CONST_KEY_CIDB],
        }

    for rec in cost_input_records_value:
        cost_input_ID = rec[pcmvf.PC_COST_INPUT_ID]

        if cost_input_ID in cost_input_DB_dict:
            rec[pcmvf.PC_COST_INPUT_STR_CONST] = cost_input_DB_dict[cost_input_ID]['costInputStringConstant']
            rec[pcmvf.PC_COST_INPUT_UNIT_OF_MEASURE_STR_CONST] = cost_input_DB_dict[cost_input_ID]['unitOfMeasureStringConstant']

def _reload_string_constants_cost_sections_and_subsections(cost_section_records_value):
    # Both come from the same database.
    cost_section_DB = pcrd.PC_load_costing_sect_DB()

    ##########   Costing sections   ###########

    # Create a dict out of the database for fast lookups. Key-value pairs are keys described below and costing string constants.
    cost_sections_DB_dict = {}

    for cost_section_DB_obj in cost_section_DB:
        # Costing section rows will have a subsection ID of -1
        costing_subsect_ID = cost_section_DB_obj[pcdbk.PC_COSTING_SUBSECT_ID_KEY_CSDB]

        if costing_subsect_ID == -1:
            # Costing sections require area type ID, area ID, and costing section ID to positively identify them.
            key = (str(
                str(cost_section_DB_obj[pcdbk.PC_AREA_TYPE_ID_KEY_CSDB])
                + '_' + str(cost_section_DB_obj[pcdbk.PC_AREA_ID_KEY_CSDB])
                + '_' + str(cost_section_DB_obj[pcdbk.PC_COSTING_SECT_ID_KEY_CSDB])
            ))
            cost_sections_DB_dict[key] = cost_section_DB_obj[pcdbk.PC_COSTING_SECT_STR_CONST_KEY_CSDB]

    for rec in cost_section_records_value:
        area_type_ID = rec[pcmvf.PC_COSTING_SECT_AREA_TYPE_ID]
        area_ID = rec[pcmvf.PC_COSTING_SECT_AREA_ID]
        costing_sect_ID = rec[pcmvf.PC_COSTING_SECT_ID]

        key = str(str(area_type_ID) + '_' + str(area_ID) + '_' + str(costing_sect_ID))

        if key in cost_sections_DB_dict:
            rec[pcmvf.PC_COSTING_SECT_STR_CONST] = cost_sections_DB_dict[key]

    ##########   Costing subsections   ###########

    cost_subsection_DB_dict = {}

    for cost_subsection_DB_obj in cost_section_DB:
        # Costing subsection rows will have a subsection ID other than -1
        costing_subsect_ID = cost_subsection_DB_obj[pcdbk.PC_COSTING_SUBSECT_ID_KEY_CSDB]

        if costing_subsect_ID != -1:
            # Costing sections require area type ID, area ID, and costing section ID to positively identify them. Costing subsections require these in addition to
            # the costing subsection ID.
            key = (str(
                str(cost_subsection_DB_obj[pcdbk.PC_AREA_TYPE_ID_KEY_CSDB])
                + '_' + str(cost_subsection_DB_obj[pcdbk.PC_AREA_ID_KEY_CSDB])
                + '_' + str(cost_subsection_DB_obj[pcdbk.PC_COSTING_SECT_ID_KEY_CSDB])
                + '_' + str(cost_subsection_DB_obj[pcdbk.PC_COSTING_SUBSECT_ID_KEY_CSDB])
            ))
            cost_subsection_DB_dict[key] = cost_subsection_DB_obj[pcdbk.PC_COSTING_SUBSECT_STR_CONST_KEY_CSDB]

    for section_rec in cost_section_records_value:
        area_type_ID = section_rec[pcmvf.PC_COSTING_SECT_AREA_TYPE_ID]
        area_ID = section_rec[pcmvf.PC_COSTING_SECT_AREA_ID]
        costing_sect_ID = section_rec[pcmvf.PC_COSTING_SECT_ID]
        subsect_records = section_rec[pcmvf.PC_COSTING_SECT_SUBSECT_RECORDS]

        for subsect_rec in subsect_records:
            subsect_ID = subsect_rec[pcmvf.PC_COSTING_SUBSECT_ID]

            key = str(str(area_type_ID) + '_' + str(area_ID) + '_' + str(costing_sect_ID) + '_' + str(subsect_ID))

            if key in cost_subsection_DB_dict:
                subsect_rec[pcmvf.PC_COSTING_SUBSECT_STR_CONST] = cost_subsection_DB_dict[key]

def _convert_cost_input_records_1(projection, cost_input_records_tag):
    cost_input_records_value = projection[cost_input_records_tag]

    # String constants were added.

    for rec in cost_input_records_value:
        if pcmvf.PC_COST_INPUT_STR_CONST not in rec:
            rec[pcmvf.PC_COST_INPUT_STR_CONST] = ''

        if pcmvf.PC_COST_INPUT_UNIT_OF_MEASURE_STR_CONST not in rec:
            rec[pcmvf.PC_COST_INPUT_UNIT_OF_MEASURE_STR_CONST] = ''

    if cost_input_records_tag != pcmvt.PC_COST_INPUT_RECORDS_TAG:
        projection[pcmvt.PC_COST_INPUT_RECORDS_TAG] = deepcopy(cost_input_records_value)

def _convert_cost_input_records_2(projection, cost_input_records_tag):
    cost_input_records_value = projection[cost_input_records_tag]

    _reload_string_constants_cost_inputs(cost_input_records_value)

    if cost_input_records_tag != pcmvt.PC_COST_INPUT_RECORDS_TAG:
        projection[pcmvt.PC_COST_INPUT_RECORDS_TAG] = deepcopy(cost_input_records_value)

def _convert_act_records_1(projection, act_records_tag):
    act_records_value = projection[act_records_tag]

    for rec in act_records_value:
        if pcmvf.PC_ACT_STR_CONST not in rec:
            rec[pcmvf.PC_ACT_STR_CONST] = ''

    if act_records_tag != pcmvt.PC_ACT_RECORDS_TAG:
        projection[pcmvt.PC_ACT_RECORDS_TAG] = deepcopy(act_records_value)

def _convert_costing_sect_records_1(projection, costing_sect_records_tag):
    cost_section_records_value = projection[costing_sect_records_tag]

    _reload_string_constants_cost_sections_and_subsections(cost_section_records_value)

    if costing_sect_records_tag != pcmvt.PC_COSTING_SECT_RECORDS_TAG:
        projection[pcmvt.PC_COSTING_SECT_RECORDS_TAG] = deepcopy(cost_section_records_value)

def _remove_deleted_activities_and_line_items(projection):
    def remove_deleted_line_items(line_item_mv_tag, section_mv_tag):
        # Removes line items (cost inputs) that do not have a matching section or subsection
        if line_item_mv_tag in projection:
            ALI_records = projection[line_item_mv_tag]
            existing_section_records = projection[section_mv_tag]

            for ALI_rec in ALI_records:
                costing_sect_ID = ALI_rec[pcmvf.PC_ALI_COSTING_SECT_ID]
                costing_subsect_ID = ALI_rec[pcmvf.PC_ALI_COSTING_SUBSECT_ID]
                area_ID = ALI_rec[pcmvf.PC_ALI_AREA_ID]
                area_type_ID = ALI_rec[pcmvf.PC_ALI_AREA_TYPE_ID]
                act_ID = ALI_rec[pcmvf.PC_ALI_ACT_ID]
                cost_input_ID = ALI_rec[pcmvf.PC_ALI_COST_INPUT_ID]

                matching_section = pcmvu.get_PC_costing_sect_rec(existing_section_records, area_type_ID, area_ID, costing_sect_ID)
                matching_subsection = pcmvu.get_PC_costing_subsect_rec(existing_section_records, area_type_ID, area_ID, costing_sect_ID, costing_subsect_ID)

                if matching_section == gbc.GB_NOT_FOUND or matching_subsection == gbc.GB_NOT_FOUND:
                    ALI_records = pcmvu.remove_PC_ALI_rec(ALI_records, area_type_ID, area_ID, costing_sect_ID, costing_subsect_ID, act_ID, cost_input_ID)
            
            projection[line_item_mv_tag] = deepcopy(ALI_records)

    def remove_deleted_activities(activity_mv_tag, section_mv_tag):
        # Removes activities that do not have a matching section or subsection
        if activity_mv_tag in projection:
            act_records = projection[activity_mv_tag]
            existing_section_records = projection[section_mv_tag]

            for activity_rec in act_records:
                costing_sect_ID = activity_rec[pcmvf.PC_ACT_COSTING_SECT_ID]
                costing_subsect_ID = activity_rec[pcmvf.PC_ACT_COSTING_SUBSECT_ID]
                area_ID = activity_rec[pcmvf.PC_ACT_AREA_ID]
                area_type_ID = activity_rec[pcmvf.PC_ACT_AREA_TYPE_ID]
                act_ID = activity_rec[pcmvf.PC_ACT_ID]

                matching_section = pcmvu.get_PC_costing_sect_rec(existing_section_records, area_type_ID, area_ID, costing_sect_ID)
                matching_subsection = pcmvu.get_PC_costing_subsect_rec(existing_section_records, area_type_ID, area_ID, costing_sect_ID, costing_subsect_ID)

                if matching_section == gbc.GB_NOT_FOUND or matching_subsection == gbc.GB_NOT_FOUND:
                    act_records = pcmvu.remove_PC_act_rec(act_records, area_type_ID, area_ID, costing_sect_ID, costing_subsect_ID, act_ID)

            projection[activity_mv_tag] = deepcopy(act_records)

    # Needs to be done for both regular programme costing and PPLI, as they have separate modvars
    remove_deleted_line_items(pcmvt.PC_ACT_LINE_ITEM_RECORDS_TAG, pcmvt.PC_COSTING_SECT_RECORDS_TAG)
    remove_deleted_line_items(pcmvt.PC_ACT_LINE_ITEM_RECORDS_PPLI_TAG, pcmvt.PC_COSTING_SECT_RECORDS_PPLI_TAG)

    remove_deleted_activities(pcmvt.PC_ACT_RECORDS_TAG, pcmvt.PC_COSTING_SECT_RECORDS_TAG)
    remove_deleted_activities(pcmvt.PC_ACT_RECORDS_PPLI_TAG, pcmvt.PC_COSTING_SECT_RECORDS_PPLI_TAG)