import logging
import sys
import os
import logging.handlers

from argparse import ArgumentParser
from datetime import datetime as dt

import validate_data
import check_unique_teis_id
import check_unique_projpolyid
import check_common_proj_attributes


class ArcPyLogHandler(logging.StreamHandler):
    """
    Custom logging class that bounces messages to the arcpy tool window as well
    as reflecting back to the file.
    """

    def emit(self, record):
        """
        Write the log message
        """
        try:
            msg = record.msg.format(record.args)
        except:
            msg = record.msg

        if record.levelno == logging.ERROR:
            arcpy.AddError(msg)
        elif record.levelno == logging.WARNING:
            arcpy.AddWarning(msg)
        elif record.levelno == logging.INFO:
            arcpy.AddMessage(msg)
        else:
            arcpy.AddMessage(msg)

        super(ArcPyLogHandler, self).emit(record)


###############################################################################
# helper (manipulates handlers for the logic of the application)

def run_app():
    """Run the logic of application"""
    gdb, logger = get_input_parameter()
    validate_load(gdb, logger)


###############################################################################
# handler (defines core functions of the application)

def get_input_parameter():
    """
    Parse arguments and set up logger

    :return: directory where "Data Loading Directory" will be created, version of TEIS data load process
    :rtype: str, str
    """
    try:
        parser = ArgumentParser(
            description='Run this script to validate data that has either been loaded into the Stage_1.gdb or '
                        'added into the TEI_Long_Tbl through conventional editing.')
        parser.add_argument('gdb', help='Path to the Stage_1 geodatabase within the contractor package.')
        parser.add_argument('--log_level', default='INFO', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
                            help='Log level')
        parser.add_argument('--log_dir', help='Path to log Directory')
        args = parser.parse_args()

        log_name = 'main_logger'
        logger = logging.getLogger(log_name)
        logger.handlers = []

        log_fmt = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        log_file_base_name = os.path.basename(sys.argv[0])
        log_file_extension = 'log'
        timestamp = dt.now().strftime('%Y-%m-%d_%H-%M-%S')
        log_file = "{}_{}.{}".format(timestamp, log_file_base_name, log_file_extension)

        logger.setLevel(args.log_level)

        sh = logging.StreamHandler()
        sh.setLevel(args.log_level)
        sh.setFormatter(log_fmt)
        logger.addHandler(sh)

        args.log_dir = os.path.join(os.path.dirname(sys.argv[0]), 'log')
        if args.log_dir:
            try:
                os.makedirs(args.log_dir)
            except OSError:
                pass

            fh = logging.FileHandler(os.path.join(args.log_dir, log_file))
            fh.setLevel(args.log_level)
            fh.setFormatter(log_fmt)
            logger.addHandler(fh)

            if args.gdb:
                loading_log_file = os.path.join(os.path.dirname(args.gdb), 'Loading_Log.log')
                if os.path.exists(loading_log_file):
                    os.remove(loading_log_file)
                fh2 = logging.FileHandler(loading_log_file)
                fh2.setLevel(args.log_level)
                fh2.setFormatter(log_fmt)
                logger.addHandler(fh2)

        if os.path.basename(sys.executable).lower() == 'python.exe':
            arc_env = False
        else:
            arc_env = True

        if arc_env:
            arc_handler = ArcPyLogHandler()
            arc_handler.setLevel(args.log_level)
            arc_handler.setFormatter(log_fmt)
            logger.addHandler(arc_handler)

        # Start the script
        return args.gdb, logger

    except Exception as e:
        logging.exception('Unexpected exception. Program terminating.')
        exit(1)


def validate_load(gdb, logger):
    logger.info('####Initializing validate_load script')
    arcpy.env.overwriteOutput = True
    logger.info('Checking input parameters\n')

    # Check if the Stage 1 geodatabase exists
    if not os.path.exists(gdb):
        logger.error('The geodatabase {} specified does not exist.  Program terminating.'.format(gdb))
        sys.exit(1)
    logger.info('****START OF USER INPUT PARAMETERS****')
    logger.info('Stage_1 geodatabase: {}'.format(gdb))
    logger.info('****END OF USER INPUT PARAMETERS****\n')

    logger.info('****START OF DERIVED PARAMETERS****')
    fc = os.path.join(gdb, 'TEI_Long_Tbl')

    # Check if the input feature class exists
    if not arcpy.Exists(fc):
        logger.error('The input feature class {} specified does not exist. Program terminating.'.format(fc))
        sys.exit(1)
    logger.info('Feature class to validate: {}'.format(fc))

    # Check if the Tools and Templates geodatabase exists
    tat_database = os.path.join(os.path.dirname(gdb), 'Tools_and_Templates.gdb')
    if not os.path.exists(tat_database):
        logger.error('The Tools and Templates geodatabase does not exist, please ensure the contractor'
                     ' package was extracted correctly.  Program terminating.')
        sys.exit(1)
    logger.info('Tools and Templates geodatabase: {}'.format(tat_database))

    logger.info('****END OF DERIVED PARAMETERS****\n')

    lst_bapids = []
    with arcpy.da.SearchCursor(fc, 'BAPID') as s_cursor:
        for row in s_cursor:
            if row[0] not in lst_bapids:
                lst_bapids.append(row[0])

    lst_bapids.sort()
    logger.info('The following BAPIDs were detected in the input feature class: {}'.format(lst_bapids))

    check_common_proj_attributes.check_common_proj_attributes(gdb, logger)
    
    check_unique_teis_id.check_unique_teis_id(fc, 'NoRenumber', logger)

    check_unique_projpolyid.check_unique_projpolyid(fc, logger)

    logger.info('Validating the TEI_Long_Tbl')
    validate_data.validate_data(tat_database, fc, logger)

    tei_lng_tbl = os.path.basename(fc)

    crit_err_field_tbl = "{}_CriticalFieldErrors".format(tei_lng_tbl)
    crit_err_tbl = "{}_CriticalRangeErrors".format(tei_lng_tbl)
    domain_err_tbl = "{}_DomainErrors".format(tei_lng_tbl)
    geom_err_tbl = "{}_GeometryErrors".format(tei_lng_tbl)
    mand_err_tbl = "{}_MandatoryFieldErrors".format(tei_lng_tbl)
    range_err_tbl = "{}_RangeErrors".format(tei_lng_tbl)
    row_err_tbl = "{}_RowErrors".format(tei_lng_tbl)
    top_err_tbl = "{}_TopologyErrors".format(tei_lng_tbl)
    summ_err_tbl = "{}_SummaryErrors".format(tei_lng_tbl)

    warn_msg = []
    err_msg = []

    # check the error tables
    error_tables = [crit_err_tbl, crit_err_field_tbl, domain_err_tbl, geom_err_tbl, mand_err_tbl, range_err_tbl,
                    row_err_tbl, top_err_tbl, summ_err_tbl]

    arcpy.env.workspace = gdb
    stage1_tables = arcpy.ListTables() + arcpy.ListFeatureClasses()
    geom_errors = False

    for tbl in stage1_tables:
        if tbl in error_tables:
            if tbl not in [crit_err_tbl, crit_err_field_tbl, top_err_tbl]:
                row_count = int(arcpy.GetCount_management(tbl)[0])
                if row_count > 0:
                    if tbl == geom_err_tbl:
                        geom_errors = True
                    warn_msg.append("{} error(s) in {}".format(row_count, tbl))
            else:
                crit_row_count = int(arcpy.GetCount_management(tbl)[0])
                if crit_row_count > 0:
                    err_msg.append("There are {} errors in {}, fix and re-run script".
                                   format(crit_row_count, tbl))
                else:
                    logger.info("No errors found in {}, data is ready to be amalgamated".format(tbl))

    # print warning messages
    for message in warn_msg:
        logger.warning(message)

    for message in err_msg:
        logger.error(message)

    if err_msg:
        sys.exit(1)

    if geom_errors:
        logger.info('Repairing geometry issues')
        arcpy.RepairGeometry_management(fc, 'KEEP_NULL')

    lst_bapids = []
    with arcpy.da.SearchCursor(fc, 'BAPID') as s_cursor:
        for row in s_cursor:
            if row[0] not in lst_bapids:
                lst_bapids.append(row[0])

    arcpy.env.workspace = gdb
    lst_fc = arcpy.ListFeatureClasses()
    lst_tbl = arcpy.ListTables()
    for bapid in lst_bapids:
        logger.info('Creating submission for BAPID {}'.format(bapid))
        op_gdb = os.path.join(os.path.dirname(gdb), 'Operational_Data_{}.gdb'.format(bapid))
        if arcpy.Exists(op_gdb):
            logger.warning('{} already exists - DELETING.'.format(op_gdb))
            arcpy.Delete_management(op_gdb)
        logger.info('Creating {}'.format(op_gdb))
        arcpy.CreateFileGDB_management(os.path.dirname(gdb), 'Operational_Data_{}.gdb'.format(bapid))

        for f_class in lst_fc:
            f_lyr = arcpy.MakeFeatureLayer_management(f_class, 'f_lyr', 'BAPID = {}'.format(bapid))
            if int(arcpy.GetCount_management(f_lyr).getOutput(0)) > 0 or f_class in ['TEI_Long_Tbl_TopologyErrors']:
                logger.info('Copying {} to Operational_Data_{}.gdb'.format(f_class, bapid))
                arcpy.CopyFeatures_management(f_lyr, os.path.join(op_gdb, f_class))
            arcpy.Delete_management(f_lyr)

        for tbl in lst_tbl:
            t_vw = arcpy.MakeTableView_management(tbl, 't_vw', 'BAPID = {}'.format(bapid))
            if int(arcpy.GetCount_management(t_vw).getOutput(0)) > 0 or \
                    tbl not in ['TEI_Usr_Dfn_Data', 'TEI_Usr_Dfn_Fields', 'TEI_Usr_Dfn_Domains']:
                logger.info('Copying {} to Operational_Data_{}.gdb'.format(tbl, bapid))
                arcpy.TableSelect_analysis(tbl, os.path.join(op_gdb, tbl), 'BAPID = {}'.format(bapid))
            arcpy.Delete_management(t_vw)

        tbl_eco_codes = 'TEI_Ecosystem_Code_Submission'
        if arcpy.Exists(tbl_eco_codes):
            arcpy.Copy_management(tbl_eco_codes, os.path.join(op_gdb, tbl_eco_codes))

    logger.info('####Completed validate_load script')


###############################################################################
# register event (runs the helper with different configurations)

if __name__ == '__main__':
    try:
        # Import arcpy
        import arcpy
    except:
        logging.error('No ArcGIS licenses available to run this tool.  Program terminating.')
        sys.exit(1)
    run_app()
else:
    try:
        # Import arcpy
        import arcpy
    except:
        logging.error('No ArcGIS licenses available to run this tool.  Program terminating.')
        sys.exit(1)
