__all__ = ['evaluate_pipeline']

from ._imports import *
from ._pconfig import *
from ._train import dataset_target, evaluate_metrics
from ._misc import *	
from ._features import feature_cancel_renewal, feature_cancel_expiring

def evaluate_pipeline(run_start, run_end, env='dev', logger=None, slicer=None):
    """
    Evaluation pipeline that loads past scores, pulls target within time period, 
    and evaluate performance for binary classification.

    Parameters
    ----------
    run_start : str
        The start month string in '%Y-%m' format.
    run_end : str
        The end month string in '%Y-%m' format.
    env : str
        The run environment, 'dev' or 'prod'.
    logger : logging.RootLogger
        Logger for information, warnings, and errors.
    slicer : function
        Function to divides data into subgroup for evaluation.
        Default (None) evaluates on the entire dataset.
        Example: slicer = lambda r: 'Over 60' if r['Age']>60 else 'Under 60'

    Returns
    -------
    int
        Binary integer for error status.
    """

    error_status = 0
    trackers = {}
    impacts  = {}
    if logger:
        logger.info(f'Evaluate from {run_start} to {run_end} on {env}.')
    try:
        runtime   = create_evaluate_runtime(run_start, run_end, env, logger)
        df_scores = dataset_load_scores(runtime)
        if len(df_scores)==0:
            raise('No Records to Evaluate')
        df_target = dataset_target_initialize(df_scores, runtime)
        df_target = dataset_target(df_target, runtime)
        df        = pd.merge(df_scores, df_target[['POLICY_KEY', pconfig['target']]], how='left', on=['POLICY_KEY'])

        evals    = evaluate_metrics(df[pconfig['target']], df['PROBABILITY'], df['PREDICTION'])
        trackers = {'all': evals}
        if slicer:
            evals_slicer = evaluate_metrics_by_slice(df, slicer)
            trackers     = trackers | evals_slicer
        trackers['error'] = error_status

        impacts = impact_metrics(df, runtime)

    except Exception as e:
        if logger:
            logger.error('Error', exc_info=e)
        error_status = 1
        trackers     = {'error': error_status}
    finally:
        save_evals_mstr(trackers, runtime)
        save_impact_mstr(impacts, runtime)
        return error_status

def create_evaluate_runtime(run_start, run_end, env, logger):
    runtime                   = {}
    runtime['timestamp']      = dt.datetime.now().strftime("%Y%m%d%H%M%S")
    runtime['env']            = env
    runtime['logger']         = logger
    runtime['teradata_table'] = f'{config["teradata"]["user"]}.temp_table'
    runtime['genesys_table']  = '#tmp_phone_number'
    runtime['type_dt']        = pconfig['type_dt']

    run_start = dt.datetime.strptime(run_start, '%Y-%m')
    run_end   = dt.datetime.strptime(run_end, '%Y-%m')
    run_date  = DtFind.first_day_last_month(run_start).replace(day=pconfig['run_day'])
    runtime['run_start']      = DtFind.first_day_of_month(run_start).strftime('%Y-%m-%d')
    runtime['run_end']        = DtFind.last_day_of_month(run_end).strftime('%Y-%m-%d')

    runtime['run_date']       = run_date.strftime('%Y-%m-%d')
    runtime['run_cutoff']     = DtFind.first_day_of_month(run_date + dt.timedelta(3 * 30)).replace(day=pconfig['run_day']).strftime('%Y-%m-%d')

    runtime['features']       =  {'CANCEL_RENEWAL': True, 'CANCEL_EXPIRING': True}
    return runtime  

def evaluate_metrics_by_slice(df, slicer):
    trackers = {}
    slices = df.apply(slicer, axis=1)
    for s in slices.unique():
        df_slice    = df.loc[slices==s]
        evals       = evaluate_metrics(df_slice[pconfig['target']], df_slice['PROBABILITY'], df_slice['PREDICTION'])
        trackers[s] = evals
    return trackers

def impact_metrics(df, runtime):
    #df_care    = dataset_phone_care(df.loc[df['PREDICTION']==1], runtime)
    df_care    = dataset_phone_care_not_available (df)
    df_genesys = dataset_phone_genesys(df.loc[df['PREDICTION']==1], runtime)
    df_impact  = pd.concat([
        df_care.loc[(df_care['PREDICTION']==1) & (df_care[pconfig['target']]==0) & (df_care['calldurationinseconds']>0), ['ACCOUNT_NO','PREMIUM_RENEWAL']],
        df_genesys.loc[(df_genesys['PREDICTION']==1) & (df_genesys[pconfig['target']]==0) & (df_genesys['SegmentTime']>0), ['ACCOUNT_NO','PREMIUM_RENEWAL']],
    ], ignore_index=True).drop_duplicates()

    impact_care = {
        'accounts predicted cancel':          df_care['ACCOUNT_NO'].nunique(),
        'accounts predicted cancel & called': df_care.dropna(subset=['calldurationinseconds'])['ACCOUNT_NO'].nunique(),
        'calls total':                        int(df_care['calldurationinseconds'].count()),
        'calls inbound':                      int(df_care.apply(lambda x: x['calldurationinseconds'] if x['calltype']=='Inbound' else None, axis=1).count()),
        'calls outbound':                     int(df_care.apply(lambda x: x['calldurationinseconds'] if x['calltype']=='Outbound' else None, axis =1).count()),
        'average call duration (sec)':        np.round(df_care['calldurationinseconds'].mean(), 2),
    }
    impact_genesys = {
        'accounts predicted cancel':          df_genesys['ACCOUNT_NO'].nunique(),
        'accounts predicted cancel & called': df_genesys.dropna(subset=['SegmentTime'])['ACCOUNT_NO'].nunique(),
        'calls total':                        int(df_genesys['SegmentTime'].count()),
        'calls inbound':                      int(df_genesys.apply(lambda x: x['SegmentTime'] if x['direction']=='inbound' else None, axis=1).count()),
        'calls outbound':                     int(df_genesys.apply(lambda x: x['SegmentTime'] if x['direction']=='outbound' else None, axis=1).count()),        
        'average call duration (sec)':        np.round(df_genesys['SegmentTime'].mean(), 2),
    }

    impact_retained = {
        'premium gained':    int(df_impact['PREMIUM_RENEWAL'].sum()),
        'accounts retained': len(df_impact), 
    }

    impacts = {
        'care':     impact_care,
        'genesys':  impact_genesys,
        'retained': impact_retained,
    }

    return impacts

def dataset_load_scores(runtime):
    fnames = s3_list_folder_contents(pconfig[f's3_model_output_eds_folder_{runtime["env"]}'], env=runtime['env'])
    fnames = pd.DataFrame({'file': fnames})
    pat    = r'cancel_([\d\-]*)_([\d\-]*)_([\d]*)\.csv'
    fnames[['run_month', 'run_date', 'timestamp']] = fnames['file'].astype(str).str.extract(pat)
    fnames = fnames.loc[
        (fnames['run_month'] >= runtime['run_start']) 
        &
        (fnames['run_month'] <= runtime['run_end'])
    ]
    
    df = pd.DataFrame()
    for _, row in fnames.iterrows():
        fname  = row['file']
        ts     = row['timestamp']
        df_run = s3_download_and_read_csv(fname, env=runtime['env'])
        df_run = df_run.assign(timestamp = ts)
        df = pd.concat([df, df_run], ignore_index=True)
    df = df.sort_values(by='timestamp', ascending=False).drop_duplicates(subset='POLICY_KEY', keep='first')
    df['POLICY_KEY'] = df['POLICY_KEY'].astype(str).str.strip()
    return df

def dataset_target_initialize(df, runtime):
    table_name  = runtime['teradata_table']
    key_list    = ','.join([k.strip() for k in df['POLICY_KEY'].astype(str).tolist()])   

    table_query = f'''
        select
            trim(a.policy_key) as POLICY_KEY
            , trim(a.policy_no) as POLICY_NO	
            , trim(a.account_no) as ACCOUNT_NO	
            , trim(a.perpetual_pol_no) as PERPETUAL_POL_NO	
            , trim(a.expired_policy_no) as EXPIRED_POLICY_NO	
            , a.effective_date as EFFECTIVE_DATE	
            , a.expiration_date as EXPIRATION_DATE	
            , a.process_ts as PROCESS_TS 
        from atuprv.dw_agreement a
        where
            a.policy_type = 1
            and a.policy_activity = 'Renewal'
            and a.policy_key in ({key_list})
        qualify row_number() over (partition by a.policy_key order by a.process_ts desc)=1
    '''
    td_drop_table(table_name)
    td_volatile(table_query, table_name)
    df = td_query(query_creator(table_columns='*', table_name=table_name, table_ops=''))
    df['POLICY_KEY'] = df['POLICY_KEY'].astype(str).str.strip()

    if runtime['features']['CANCEL_RENEWAL']:
        df = feature_cancel_renewal(df, runtime)	
    if runtime['features']['CANCEL_EXPIRING']:
        df = feature_cancel_expiring (df, runtime)	
    return df

def dataset_phone_care(df, runtime):
    table_columns = '''
        pol.name as policy_number,
        FinServ__Account__c, 
        acct.name, 
        acct.personcontactid, 
        task.subject, 
        task.callType, 
        task.callobject, 
        task.calldisposition, 
        task.calldurationinseconds,
        task.createddate
    '''
    table_name = 'salesforce.finserv__alert__c alert'
    table_ops= f'''
        inner join salesforce.account acct on alert.finserv__account__c = acct.sfid
        inner join salesforce.task task on task.whoid = acct.personcontactid
        inner join salesforce.insurancepolicy pol on pol.nameinsuredid = alert.FinServ__Account__c and task.createddate between pol.effectivedate and pol.expirationdate  --eliminate 2 
        where finserv__message__c ='Retention Risk' 
        and pol.effectivedate between '{runtime['run_start']}' and '{runtime['run_end']}'
        and task.callType is not null
        and pol.line_of_business_type__c = 'Private Passenger'
        and task.createddate between '{runtime['run_date']}' and '{runtime['run_cutoff']}'
    '''
    table_query = query_creator(table_columns, table_name, table_ops)
    df_query = sf_query(table_query)

    df = df.merge(df_query, how='left', left_on='POLICY_NO', right_on='policy_number')
    return df

def dataset_phone_genesys(df, runtime):
    table_columns = '''
        agree.policy_key, agree.policy_no, trim(agree.account_no) as account_no, agrol.edb_id, pm.phone_id,
            trim('tel:+1' || phonenum.area_code || phonenum.exchange || phonenum.number_rw) as TelephoneNumber
    '''
    table_name = '''
        (
            select policy_key, policy_no, account_no, process_ts from atuprv.cust_dw_agreement_latest where policy_type=1
            qualify row_number() over (partition by account_no order by process_ts desc)=1 
        ) agree
    '''
    table_ops= f'''
        left join (
            select policy_key, edb_id from atuprv_adhoc.dw_e_agree_role_latest 
            qualify row_number() over (partition by policy_key order by seq_id)=1 
        ) agrol on agree.policy_key=agrol.policy_key
        left join (
            select edb_id, phone_id from atuprv.dw_e_party_phone
            qualify row_number() over (partition by edb_id order by process_ts desc)=1
        ) pm on agrol.edb_id = pm.edb_id
        left join atuprv.dw_e_phone phonenum on phonenum.phone_id = pm.phone_id    
        where year(agree.process_ts) > year('{runtime['run_cutoff']}')-5
    '''
    table_query = query_creator(table_columns, table_name, table_ops)
    df_mapping_query = td_query(table_query)
    df_genesys = df.merge(df_mapping_query[['account_no','TelephoneNumber']] , how='left', left_on='ACCOUNT_NO', right_on='account_no')

    gs_temp(df_genesys[['TelephoneNumber']].drop_duplicates(), runtime['genesys_table'], columns_index=['TelephoneNumber'])
    

    #optimize the query, add index 
    genesys_call_index_sql = f"""
    drop table if exists #tmp_call 

    create table #tmp_call (
    phone  varchar(300),
    userid varchar(50),
    direction varchar(10),
    SegmentTime numeric
    )
    
    insert into #tmp_call 
    select 
    iif( a.direction = 'inbound',  a.Ani, a.Dnis) as phone,
    a.USERID,
                a.direction as direction,
                round(a.tTalkcomplete/1000, 2) as SegmentTime        

    from callsegments_emite a
    where 
        a.ConversationStart between '{runtime['run_date']}' and '{runtime['run_cutoff']}'
    and a.tTalkcomplete is not null

    create index idx_phone on #tmp_call(phone) 

    select * from #tmp_call

    """
    gs_query(genesys_call_index_sql)

    genesys_phone_sql =  f"""
        select    
            a.phone,
            a.direction as direction,
            a.SegmentTime        
        from #tmp_call a
        join {runtime['genesys_table']} b on  b.TelephoneNumber  = a.phone
        join dbo.genesysCloudUsersAPI c on a.USERID = c.[entity_id]
        where 
             c.division_name like 'SCS%'
        """
    
    df_phone_query = gs_query(genesys_phone_sql)

    df_genesys = df_genesys.merge(df_phone_query[['phone', 'SegmentTime', 'direction']] , how='left', left_on='TelephoneNumber', right_on='phone')
    return df_genesys

def save_evals_mstr(data, runtime):
    s3_save_and_upload_json(
        data,
        s3_file_path = os.path.join(
            pconfig[f's3_model_output_mstr_folder_{runtime["env"]}'],
            'evaluate/',
            f'cancel_eval_{runtime["run_start"]}_{runtime["run_end"]}_{runtime["timestamp"]}.json'
        ),
        env=runtime['env'],
    )

def save_impact_mstr(data, runtime):
    s3_save_and_upload_json(
        data,
        s3_file_path = os.path.join(
            pconfig[f's3_model_output_mstr_folder_{runtime["env"]}'],
            'impact/',
            f'cancel_impact_{runtime["run_start"]}_{runtime["run_end"]}_{runtime["timestamp"]}.json'
        ),
        env=runtime['env'],
    )

def dataset_phone_care_not_available (df):
    '''
    this function is used to replace the orginal df when 
    care data source is not available, so pipeline still run
    '''
    df_care = pd.DataFrame().reindex(columns=list(df.columns) + ['policy_number', 'finserv__account__c', 'name', 'personcontactid',
       'subject', 'calltype', 'callobject', 'calldisposition',
       'calldurationinseconds', 'createddate'])
    return df_care