from __future__ import absolute_import
from datetime import datetime, timedelta
from dostadmin import app, db, db_model, app_logger
from dostadmin.db_model import Timecategory, Experience, Registration
from dostadmin.services.scheduling.pre_calculated_campaign_service import (
    PreCalculatedCampaignService,
)
from dostadmin.services.scheduling.intro_call_scheduling_service import (
    IntroCallSchedulingService,
)
from sqlalchemy import and_, desc
from utils.helpers.helpers import (
    replace_chars,
    format_partner_name,
    user_already_has_active_experience,
    content_not_found,
    partner_does_not_exist,
    duplicate_phone_numbers,
)

pre_calculated_campaign_service = PreCalculatedCampaignService()
intro_call_scheduling_service = IntroCallSchedulingService()


# noinspection PyArgumentList
def add_to_partner_table(partners):
    for p in partners:
        channel_id = db_model.Channel.query.get_by_name(replace_chars(p[3])).id
        partner_rec = db_model.Partner(
            name=replace_chars(format_partner_name(p[0])),
            area=replace_chars(p[1]),
            city=replace_chars(p[2]),
            email=replace_chars(p[4]),
            channel_id=channel_id,
        )
        db.session.add(partner_rec)
    db.session.commit()


def add_to_channel_table(channels):
    for c in channels:
        channel_rec = db_model.Channel(name=replace_chars(format_partner_name(c)))
        db.session.add(channel_rec)
    db.session.commit()


def add_experience_pause_log(pause_log_data, next_program):
    no_experience = []
    pending_pause_log = []
    program_len = db_model.Programseq.query.get_program_len(next_program)
    for pause_log in pause_log_data:
        exp = db_model.Experience.query.find_experience_with_phone_status(
            pause_log[0], db_model.Experience.Status.ACTIVE
        )
        start_date = datetime.strptime(replace_chars(pause_log[1]), "%m/%d/%Y").date()
        if exp:
            if not db_model.ExperiencePauseLog.query.pending_pause_log_exists(exp.id):
                start_date = start_date + timedelta(
                    days=-start_date.weekday() - 1, weeks=1
                )
                pause_end_date = start_date + timedelta(days=1, weeks=program_len)
                pause_log = db_model.ExperiencePauseLog(
                    experience_id=exp.id,
                    pause_start_date=start_date,
                    pause_end_date=pause_end_date,
                    next_program_id=next_program,
                    status=db_model.ExperiencePauseLog.Status.PENDING,
                )
                db.session.add(pause_log)
            else:
                pending_pause_log.append(pause_log[0])
        else:
            no_experience.append(pause_log[0])
    db.session.commit()
    return no_experience, pending_pause_log


def get_error_list_for_adding_provider_number(providernumber_data):
    errors = []
    for data in providernumber_data:
        exotel_appid = replace_chars(data[2])
        content_id = db_model.ContentVersion.query.find_content_id_with_exotel_appid(
            exotel_appid
        )

        if not content_id:
            errors.append(content_not_found(exotel_appid))
    return errors


def add_provider_number(providernumber_data):
    errors = get_error_list_for_adding_provider_number(providernumber_data)
    if not errors:
        for data in providernumber_data:
            phone = replace_chars(data[0])
            name = replace_chars(data[1])
            exotel_appid = replace_chars(data[2])
            language_name = replace_chars(data[3])
            partner_name = replace_chars(data[4])
            partner = db_model.Partner.query.get_by_name(partner_name)
            content_id = (
                db_model.ContentVersion.query.find_content_id_with_exotel_appid(
                    exotel_appid
                )
            )
            language_id = db_model.Language.query.find_language_id_with_name(
                language_name
            )

            db_model.ProviderNumber.add_provider_number(
                phone, name, content_id, language_id, partner.id
            )
    return errors


def get_error_list_for_non_mvp_users(users, provider):
    phones = {}
    duplicates = []
    errors = []
    phone = replace_chars(users[0])

    experience = db_model.Experience.query.find_experience_with_phone_status(
        phone, db_model.Experience.Status.ACTIVE
    )
    if experience:
        errors.append(user_already_has_active_experience(phone, "user"))

    if not phones.get(phone, False):
        phones[phone] = True
    else:
        duplicates.append(phone)

    partner_id = provider.partner_id

    partner = db_model.Partner.query.get_by_id(partner_id)
    if partner_id == -1:
        errors.append(partner_does_not_exist(partner_id))

    if duplicates:
        duplicate_phones = ", ".join(duplicates)
        errors.append(duplicate_phone_numbers(duplicate_phones))
    return errors, partner


def add_non_mvp_users(user, experience_type, provider, current_user):
    try:
        errors, partner = get_error_list_for_non_mvp_users(user, provider)
        if not errors:
            phone, program, block, timerange = user

            user_data = {
                "phone": replace_chars(phone),
                "experience_type": experience_type,
                "provider_number": provider.phone,
                "program": replace_chars(program),
                "block": replace_chars(block),
                "timerange": timerange,
                "partner_id": partner.id,
            }

            (
                new_user,
                new_registration,
                new_exp,
                pre_calculated_campaign_data,
            ) = add_to_users_table(
                user_data, db_model.UserType.Type.PARENT, current_user
            )

        return errors, new_user, new_registration, new_exp, pre_calculated_campaign_data
    except Exception as e:
        app_logger.error(f"Error while preparing new users data to add through UI. {e}")


# noinspection PyArgumentList
def get_partner_and_block_details(user_data):
    partner = db_model.Partner.query.get_by_id(user_data["partner_id"])
    district_block_mapping = db_model.DistrictBlockMapping.query.get_by_block_name(
        user_data["block"]
    )
    return partner.state, district_block_mapping.district


def determine_time_slot(timerange):
    time_slots_map = {
        Timecategory.TimeRange.MORNING_TIME_RANGE: [
            Timecategory.Category.EIGHT_AM,
            Timecategory.Category.NINE_AM,
            Timecategory.Category.TEN_AM,
        ],
        Timecategory.TimeRange.AFTERNOON_TIME_RANGE: [
            Timecategory.Category.TWELVE_PM,
            Timecategory.Category.ONE_PM,
            Timecategory.Category.TWO_PM,
        ],
        Timecategory.TimeRange.EVENING_TIME_RANGE: [
            Timecategory.Category.FOUR_PM,
            Timecategory.Category.FIVE_PM,
            Timecategory.Category.SIX_PM,
        ],
    }
    return time_slots_map.get(timerange, [])


def create_user_and_registration(
    user_data,
    user_type,
    state,
    district,
    timecategory,
    program_id,
    user_type_id,
    language_id,
    start_date,
    current_user,
):
    new_user = db_model.User(
        name="fnu",
        user_type_id=user_type_id,
        partner_id=user_data["partner_id"],
        program_order={},
    )
    db.session.add(new_user)
    db.session.commit()

    new_registration = db_model.Registration(
        user_id=new_user.id,
        partner_id=user_data["partner_id"],
        provider_number=user_data["provider_number"],
        phone=user_data["phone"],
        program_id=program_id,
        signup_status=db_model.Registration.SignupStatus.COMPLETE,
        time_category_id=timecategory.id,
        district=district,
        state=state,
        block=user_data["block"],
        onboarding_source=db_model.Registration.OnboardingSource.UI_REGISTERED,
        onboarded_by=current_user.username,
    )
    db.session.add(new_registration)
    db.session.commit()

    new_experience = db_model.Experience(
        user_id=new_user.id,
        timecategory_id=timecategory.id,
        language_id=language_id,
        phone=user_data["phone"],
        start_date=start_date,
        provider_number=user_data["provider_number"],
        type=user_data["experience_type"],
        status=db_model.Experience.Status.ACTIVE,
        program_id=program_id,
    )
    db.session.add(new_experience)
    db.session.commit()

    return new_user, new_registration, new_experience


def schedule_intro_campaign(new_experience):
    pre_calculated_campaign_data = pre_calculated_campaign_service.add_new_experience(
        new_experience.id
    )
    intro_call_scheduling_service.create_and_schedule_intro_campaign(
        new_experience, pre_calculated_campaign_data
    )
    return pre_calculated_campaign_data


def add_to_users_table(user_data, user_type, current_user):
    try:
        app_logger.info("Adding new user data: %s", user_data)

        state, district = get_partner_and_block_details(user_data)
        timeslots = determine_time_slot(user_data["timerange"])
        timecategory = Timecategory.query.get_least_users_timeslot(timeslots)
        program_id = db_model.Program.query.find_program_id_with_name(
            user_data["program"]
        )
        user_type_id = db_model.UserType.query.get_user_type_id_by_name(user_type)
        language_id = db_model.Language.query.find_language_id_with_name()

        start_date = datetime.now().date()
        if user_data["experience_type"] == db_model.Experience.Type.SELFPACEDPC:
            start_date += timedelta(days=1)

        new_user, new_registration, new_experience = create_user_and_registration(
            user_data,
            user_type,
            state,
            district,
            timecategory,
            program_id,
            user_type_id,
            language_id,
            start_date,
            current_user,
        )
        pre_calculated_campaign_data = schedule_intro_campaign(new_experience)

        return (
            new_user,
            new_registration,
            new_experience,
            pre_calculated_campaign_data,
        )
    except Exception as e:
        app_logger.error(f"Error occurred while adding new users: {e}")


def add_to_program(program_id, pdata):
    for p in pdata:
        pseq_rec = db_model.Programseq.query.filter(
            and_(
                db_model.Programseq.sequence_index == int(p[0]),
                db_model.Programseq.program_id == program_id,
            )
        ).first()
        if pseq_rec:
            pseq_rec.content_id = db_model.Content.query.find_content_id_with_name(p[1])
            pseq_rec.week = int(p[2])
            pseq_rec.day = int(p[3])
            pseq_rec.program_id = program_id
        else:
            pseq_rec = db_model.Programseq(
                content_id=db_model.Content.query.find_content_id_with_name(p[1]),
                program_id=program_id,
                sequence_index=int(p[0]),
                week=int(p[2]),
                day=int(p[3]),
            )
            db.session.add(pseq_rec)
    db.session.commit()


def get_appid_for_userphone(phone, exo_phone):
    if exo_phone == app.config["EXOTEL_CALLIN_PHONE"]:
        return app.config["EXOTEL_CALLIN_DEFAULT_APPID"]
    if exo_phone in (
        app.config["EXOTEL_POSTER_PHONE1"],
        app.config["EXOTEL_POSTER_PHONE2"],
    ):
        return app.config["EXOTEL_POSTER_DEFAULT_APPID"]

    exp_rec = db_model.Experience.query.filter(
        and_(db_model.Experience.phone == phone, db_model.Experience.status == "active")
    ).first()
    if exp_rec:
        last_campaign = (
            db_model.Campaign.query.filter(
                and_(
                    db_model.Campaign.experience_id == exp_rec.id,
                    db_model.Campaign.status == "completed",
                )
            )
            .order_by(desc(db_model.Campaign.deploy_datetime))
            .limit(1)
            .first()
        )

        if last_campaign and last_campaign.content:
            return last_campaign.content.exotel_appid

        # get default appid based on the current program
        first_pseq = db_model.Programseq.query.filter(
            and_(
                db_model.Programseq.program_id == exp_rec.program_id,
                db_model.Programseq.sequence_index == 1,
            )
        ).first()
        if first_pseq and first_pseq.content:
            return first_pseq.content.exotel_appid

        if not (first_pseq and first_pseq.content):
            app_logger.error("Programseq/Content not found")
    else:
        app_logger.error("User/Experience not found: New user with Phone %s ?", phone)

    return app.config["EXOTEL_DEFAULT_APPID"]


def create_cohorts(data, experiment_id):
    for val in data:
        inputs_json = {}
        outputs_json = {}
        cohort_name = replace_chars(val[0])
        start_date = datetime.strptime(replace_chars(val[1]), "%m/%d/%Y").date()
        end_date = datetime.strptime(replace_chars(val[2]), "%m/%d/%Y").date()
        inputs = val[3].rstrip().split(",")
        outputs = val[4].rstrip().split(",")
        for input_item in inputs:
            input_type_val = [x.strip() for x in input_item.split(":")]
            input_type, input_val = input_type_val
            inputs_json[input_type] = input_val
        for output_item in outputs:
            output_type_val = [x.strip() for x in output_item.split(":")]
            output_type, output_val = output_type_val
            outputs_json[output_type] = output_val
        cohort = db_model.Cohort(
            name=cohort_name,
            experiment_id=experiment_id,
            start_date=start_date,
            end_date=end_date,
            inputs=inputs_json,
            outputs=outputs_json,
        )
        db.session.add(cohort)
    db.session.commit()
