from __future__ import absolute_import
from random import randint
from events import db_model as events_model
from dostadmin.services import queries
from dostadmin.db_model import Timecategory
from dostadmin import (
    app,
    db_helper,
    exotel_helper,
    loginutils,
    adminuser,
    db_model,
    db,
    app_logger,
)
from dostadmin.services.scheduling.pre_calculated_campaign_service import (
    PreCalculatedCampaignService,
)
from dostadmin.services.scheduling.missed_call_service import MissedCallService
from dostadmin.services.scheduling.campaign_creation_service import (
    CampaignCreationService,
)
from typing import List, Tuple, Dict, Optional
from utils import config_parser, slackutils
from utils.helpers.helpers import replace_chars
from dostadmin.forms import AddProgramForm
from utils.helpers import get_db_connection, upload_file_to_drive
from utils.helpers.helpers import (
    invalid_nudge_name,
    nudge_format_message,
    user_does_not_have_active_experience,
    get_current_isttime,
    user_does_not_exist,
    user_pending_pause_log,
    blast_format_message,
    invalid_blast_name,
    user_paused_for_experiment,
    get_current_utc_time,
)
from flask import (
    current_app,
    render_template,
    redirect,
    request,
    make_response,
    flash,
    url_for,
    jsonify,
)
from itertools import islice
from sqlalchemy.orm import joinedload, load_only
from sqlalchemy import or_, func, inspect
from flask_security import login_required
from flask_login import login_user, logout_user, current_user
from flask_principal import (
    Identity,
    identity_changed,
    Permission,
    RoleNeed,
)
import ast
import csv
import codecs
import flask
import jwt
import json
import traceback
import re
from datetime import datetime, timedelta, date, time as dtime
from six.moves import zip
from io import StringIO
from config import EXOTEL_CALLS_PERMITTED, SERVER_TYPE, DRIVE_FOLDER_ID

############################################ Role based permission object ###########################################
# Check the Roles table to see all possible roles

admin_permission = Permission(RoleNeed("admin"))
champion_persmission = Permission(RoleNeed("champion"))
user_permission = Permission(RoleNeed("user"))
surveyor_permission = Permission(RoleNeed("surveyor"))
onboarder_permission = Permission(RoleNeed("onboarder"))
activity_reporter_permission = Permission(RoleNeed("activity_reporter"))
program_associate_permission = Permission(RoleNeed("program_associate"))
admin_champion_persmission = admin_permission.union(champion_persmission)
admin_onboarder_permission = admin_permission.union(onboarder_permission)
admin_surveyor_permission = admin_permission.union(surveyor_permission)
admin_activity_reporter_permission = admin_permission.union(
    activity_reporter_permission
)
db_connection = get_db_connection()
cursor = db_connection.cursor()
############################################ LOGIN LOGOUT ###########################################################


@app.route("/login", methods=["GET", "POST"])
def login():
    login_form = loginutils.LoginForm(request.form)
    error = None
    if login_form.validate_on_submit():
        user = db_model.AdminUser.query.filter(
            db_model.AdminUser.username == login_form.data["username"]
        ).first()
        if user:
            app_logger.info("%s logging in", user)
            if user.authenticate(login_form.data["password"]):
                if login_user(user):
                    identity_changed.send(
                        current_app._get_current_object(), identity=Identity(user.id)
                    )
                    if len(user.roles) == 1 and user.roles[0].name == "champion":
                        return redirect(url_for("champion_landing_page"))
                    if len(user.roles) == 1 and user.roles[0].name == "surveyor":
                        return redirect(url_for("live_call"))
                    if len(user.roles) == 1 and user.roles[0].name == "onboarder":
                        return redirect(url_for("add_users"))
                    if len(user.roles) == 1 and user.roles[0].name in (
                        "activity_reporter",
                        "program_associate",
                    ):
                        return redirect(url_for("activity_tracker_form"))

                    return redirect("/programs")
            else:
                app_logger.warning("Invalid password for %s", user)
        error = "Incorrect username or password."
    return render_template(
        "partials/login.html", current_user=current_user, form=login_form, error=error
    )


@app.route("/logout")
@login_required
def logout():
    current_user = db_model.AdminUser.query.filter(
        db_model.AdminUser.id == adminuser.get_current_adminuser_id()
    ).first()
    if current_user:
        current_user.logout()
    logout_user()
    return redirect("/login")


################################################ ROOT ###############################################################


@app.route("/")
# @login_required
def landing_page():
    user = db_model.AdminUser.query.filter(
        db_model.AdminUser.id == adminuser.get_current_adminuser_id()
    ).first()
    if user and len(user.roles) == 1 and user.roles[0].name == "champion":
        return redirect(url_for("champion_landing_page"))
    return redirect("/programs")


@app.route("/champion_home")
@admin_champion_persmission.require(http_exception=403)
def champion_landing_page():
    return render_template("partials/landing.html")


############################################## CAMPAIGNS ###############################################################
@app.route("/todays_campaigns")
@admin_permission.require(http_exception=403)
def todays_campaigns():
    try:
        istnow = get_current_utc_time() + timedelta(hours=5, minutes=30)
        current_page = request.args.get("page", 1, type=int)
        all_campaigns = db_model.Campaign.query.get_campaigns_for_date(
            istnow.date(), current_page
        )

        return flask.render_template(
            "home.html",
            active_panel="campaigns",
            active_view="campaigns",
            campaigns=all_campaigns.items,
            pagination=all_campaigns,
        )
    except Exception as e:
        app_logger.error(f"Error fetching today's campaigns: {e}")
        return "Error fetching campaigns", 500


@app.route("/campaigns")
@admin_permission.require(http_exception=403)
def campaigns_panel():
    # campaigns = list()
    campaigns = db_model.Campaign.query.get_campaigns_log()
    return flask.render_template(
        "home.html",
        active_panel="campaigns",
        active_view="campaigns",
        campaigns=campaigns,
    )


################################################### Schedule Announcements #######################################################

BATCH_SIZE = 500
IST_TZ = get_current_isttime().tzinfo

VALID_CP_RE = re.compile(r"^[A-Za-z0-9_-]+$")


def flash_and_redirect_errors(
    errors: List[str], redirect_to: str = "/schedule_announcements"
):
    for e in errors:
        flash(e, "error")
    return flask.redirect(redirect_to)


def normalize_and_dedupe_phones(raw: str):
    seen, out = set(), []
    for line in (raw or "").strip().splitlines():
        last10 = line.strip()[-10:]
        if last10.isdigit() and last10 not in seen:
            seen.add(last10)
            out.append(last10)
    return out


def parse_form_inputs():
    """Return (errors, cp_name, content_id, deploy_date, deploy_time_str)"""
    errors: List[str] = []

    cp_name = request.form.get("cp_name", "").strip()
    if not VALID_CP_RE.match(cp_name):
        errors.append(invalid_blast_name())

    try:
        content_id = int(request.form["audio_sms"])
    except Exception:
        content_id = None
        errors.append("Invalid content selected.")

    try:
        deploy_date = datetime.strptime(
            replace_chars(request.form["deploy_date"]), "%m/%d/%Y"
        ).date()
    except Exception:
        deploy_date = None
        errors.append("Invalid deploy date.")

    deploy_time_str = (request.form.get("time_category") or "").strip()  # "HH:MM[:SS]"

    return errors, cp_name, content_id, deploy_date, deploy_time_str


def get_phone_last10_expr():
    """Return a SQL expression extracting last 10 digits from Experience.phone (dialect-portable)."""
    PHONE_COL = db_model.Experience.phone
    bind = (
        db.session.get_bind(mapper=db_model.Experience)
        or db.session.get_bind()
        or getattr(db, "engine", None)
    )
    if bind is None:
        raise RuntimeError("No SQLAlchemy engine bound to the session/app.")
    name = bind.dialect.name
    if name == "mysql":
        return func.right(PHONE_COL, 10)
    if name in ("postgresql", "postgres"):
        return func.substring(PHONE_COL, func.length(PHONE_COL) - 9, 10)
    return func.substr(PHONE_COL, -10, 10)


def load_experiences_by_last10(phones: List[str]):
    PHONE_COL = db_model.Experience.phone
    phone_last10_expr = get_phone_last10_expr()

    exps = (
        db_model.Experience.query.options(
            joinedload(db_model.Experience.time_category).load_only(
                db_model.Timecategory.id, db_model.Timecategory.time
            ),
            load_only(
                db_model.Experience.id,
                db_model.Experience.phone,
                db_model.Experience.status,
                db_model.Experience.provider_number,
                db_model.Experience.timecategory_id,
            ),
        )
        .filter(or_(PHONE_COL.in_(phones), phone_last10_expr.in_(phones)))
        .all()
    )
    return {(e.phone or "")[-10:]: e for e in exps}


def parse_fixed_time(deploy_time_str: str):
    """Return (time_of_day, error_msg)."""
    if not deploy_time_str:
        return None, None
    try:
        fmt = "%H:%M:%S" if deploy_time_str.count(":") == 2 else "%H:%M"
        t = datetime.strptime(deploy_time_str, fmt).time()
        return dtime(t.hour, t.minute, t.second if fmt == "%H:%M:%S" else 0), None
    except Exception:
        return None, "Invalid time category."


def resolve_user_time(exp_rec, fallback: str = "10:00:00"):
    """Resolve time-of-day from relationship or fallback."""
    time_str = getattr(getattr(exp_rec, "time_category", None), "time", None)
    if not time_str and getattr(exp_rec, "timecategory_id", None):
        tc = db_model.Timecategory.query.get(exp_rec.timecategory_id)
        time_str = tc.time if tc else None
    if not time_str:
        time_str = fallback
    fmt = "%H:%M:%S" if time_str.count(":") == 2 else "%H:%M"
    t = datetime.strptime(time_str, fmt).time()
    return dtime(t.hour, t.minute, t.second if fmt == "%H:%M:%S" else 0)


def combine_date_time_tz(d: datetime.date, t: dtime):
    dt = datetime.combine(d, t)
    if IST_TZ:
        if hasattr(IST_TZ, "localize"):  # pytz
            return IST_TZ.localize(dt)
        return dt.replace(tzinfo=IST_TZ)  # zoneinfo
    return dt


def build_exp_list(
    phones: List[str],
    exp_by_phone: Dict[str, "db_model.Experience"],
    parsed_fixed_time: Optional[dtime],
    deploy_date: datetime.date,
    taf_numbers: set,
):
    exp_list: List[Tuple["db_model.Experience", datetime]] = []
    errors: List[str] = []

    for ph in phones:
        exp_rec = exp_by_phone.get(ph)
        if not exp_rec:
            errors.append(user_does_not_exist(ph))
            continue

        prov_last10 = (exp_rec.provider_number or "")[-10:]
        if (
            exp_rec.status == db_model.Experience.Status.PAUSED
            and prov_last10 in taf_numbers
        ):
            errors.append(user_paused_for_experiment(ph))
            continue

        tod = parsed_fixed_time or resolve_user_time(exp_rec)
        deploy_dt = combine_date_time_tz(deploy_date, tod)
        exp_list.append((exp_rec, deploy_dt))

    return exp_list, errors


def chunked(iterable, size):
    it = iter(iterable)
    while True:
        batch = list(islice(it, size))
        if not batch:
            break
        yield batch


def schedule_in_batches(
    cp_name: str,
    content_id: int,
    exp_list: List[Tuple["db_model.Experience", datetime]],
):
    svc = CampaignCreationService()
    total = 0
    for batch in chunked(exp_list, BATCH_SIZE):
        for exp_rec, _ in batch:
            if inspect(exp_rec).detached:
                db.session.add(exp_rec)
        svc.add_blast_campaign(cp_name, content_id, batch)
        db.session.commit()
        total += len(batch)
    return total


@app.route("/schedule_announcements", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def schedule_announcements():
    if request.method == "GET":
        content = db_model.Content.query.get_content_log()
        time_choices = Timecategory.AnnouncementTimeCategory.CHOICES
        return render_template(
            "partials/campaign_form.html",
            title="Announcements",
            form_action="/schedule_announcements",
            blast_message=blast_format_message(),
            content=content,
            time_category=time_choices,
        )

    try:
        errors, cp_name, content_id, deploy_date, deploy_time_str = parse_form_inputs()

        phones = normalize_and_dedupe_phones(request.form.get("phones", ""))
        if not phones:
            errors.append("No valid phone numbers provided.")

        if errors:
            return flash_and_redirect_errors(errors)

        taf_numbers = set(app.config.get("TAF_EXPERIMENT_PROVIDER_NUMBERS", []))
        exp_by_phone = load_experiences_by_last10(phones)

        fixed_time, t_err = parse_fixed_time(deploy_time_str)
        if t_err:
            return flash_and_redirect_errors([t_err])

        exp_list, list_errors = build_exp_list(
            phones=phones,
            exp_by_phone=exp_by_phone,
            parsed_fixed_time=fixed_time,
            deploy_date=deploy_date,
            taf_numbers=taf_numbers,
        )
        if list_errors:
            return flash_and_redirect_errors(list_errors)

        total = schedule_in_batches(cp_name, content_id, exp_list)
        flash(
            f"Queued {total} announcements for {cp_name} in batches of {BATCH_SIZE}.",
            "success",
        )

    except Exception as e:
        db.session.rollback()
        flash(f"An unexpected error occurred: {str(e)}", "error")

    return flask.redirect("/schedule_announcements")


############################################## MANAGE USER DATA ###############################################################


@app.route("/manage_user_data", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def delete_user_data():
    if request.method == "GET":
        return render_template("/partials/manage_user_data.html")
    user_phone = request.form.get("user_phone", "").strip()[-10:]
    try:
        delete_missed_calls_data(user_phone)
        delete_campaigns_and_experience_data(user_phone)
        delete_registration_data(user_phone)

        flash(
            "success", f"User data for phone number {user_phone} deleted successfully"
        )
        app_logger.info(f"User data for phone number {user_phone} deleted successfully")
    except Exception as e:
        error_message = f"Error while deleting user data for phone number {user_phone}. Error: {str(e)}"
        flash("error", error_message)
        app_logger.error(error_message)

    return render_template("/partials/manage_user_data.html")


def remove_bigquery_records(user_phone, table_name, exp_id):
    if SERVER_TYPE != "production":
        app_logger.info(
            f"Skipping BigQuery record removal for {user_phone} in non-production environment."
        )
        return
    if not table_name:
        return

    try:
        queries.remove_bigquery_table_records.delete_admidashboard_records(
            user_phone, table_name, exp_id
        )
        app_logger.info(
            f"Removed {table_name} records from BigQuery for user phone: {user_phone}."
        )
    except Exception as e:
        app_logger.error(f"Error removing {table_name} records from BigQuery: {str(e)}")


def delete_missed_calls_data(user_phone):
    db_model.MissedCallLog.delete_missed_calls_for_phone(user_phone)
    remove_bigquery_records(user_phone=user_phone, table_name="engagement", exp_id=None)


def delete_campaigns_and_experience_data(user_phone):
    experience_data = db_model.Experience.query.get_all_experience_for_phone(user_phone)
    for experience in experience_data:
        db_model.Campaign.delete_campaigns_for_experience_id(experience.id)
        remove_bigquery_records(
            user_phone=None, table_name="campaign", exp_id=experience.id
        )

        db_model.PreCalculatedUserCampaignData.delete_pre_calculated_data_for_experience_id(
            experience.id
        )

        db_model.ExperiencePauseLog.delete_experience_pause_log(experience.id)
        remove_bigquery_records(
            user_phone=None, table_name="experience_pause_log", exp_id=experience.id
        )

        db_model.Experience.delete_experience(experience)
        remove_bigquery_records(
            user_phone=None, table_name="user_program", exp_id=experience.id
        )


def delete_registration_data(user_phone):
    db_model.Registration.delete_registration_for_phone(user_phone)
    remove_bigquery_records(
        user_phone=user_phone, table_name="registration", exp_id=None
    )


@app.route("/system_config", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def system_configuration():
    if request.method == "GET":
        configurations = db_model.SystemConfiguration.query.get_all_configs()
        return render_template(
            "/partials/system_config.html", configurations=configurations
        )
    if request.method == "POST":
        config_name = request.form["config_name"]
        config_value = request.form["config_value"]
        config_instance = db_model.SystemConfiguration()
        error = False
        try:
            config_instance.set_config(config_name, config_value)
        except Exception:
            error = True

        configurations = db_model.SystemConfiguration.query.get_all_configs()
        return render_template(
            "/partials/system_config.html", configurations=configurations, error=error
        )
    return None


@app.route("/schedule_nudge", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def schedule_nudge():
    if request.method == "GET":
        contents = db_model.Content.query.get_content_log()
        query = (
            "select group_version.id as id, conditions, version_number, is_active_version, name "
            "FROM groups RIGHT JOIN group_version ON group_version.group_id = groups.id "
            "WHERE group_version.is_active_version = 'yes'"
        )
        cursor.execute(query)
        groups = cursor.fetchall()
        time_choices = Timecategory.AnnouncementTimeCategory.CHOICES
        return render_template(
            "partials/nudge_form.html",
            title="Nudges",
            form_action="/schedule_nudge",
            contents=contents,
            time_category=time_choices,
            nudge_message=nudge_format_message(),
            groups=groups,
        )

    message = []
    error_list = []
    nudge_name = request.form["cp_name"]
    splitted_name = nudge_name.split("_")

    if len(splitted_name) != 2:
        error_list.append(invalid_nudge_name())

    content_id = int(request.form["audio_sms"])
    group_version_id = int(request.form["group"])
    deploy_time = db_model.Nudges.Time.USER_TIME_SLOT

    if request.form["time_category"] != "":
        deploy_time = request.form["time_category"]

    deploy_date = datetime.strptime(
        replace_chars(request.form["deploy_date"]), "%m/%d/%Y"
    ).date()

    repeat_frequency = request.form["repeat_frequency"]

    if not error_list:
        nudge_details = db_model.Nudges(
            group_version_id=group_version_id,
            name=nudge_name,
            deployment_date=deploy_date,
            deployment_time=deploy_time,
            nudge_repeat_frequency=repeat_frequency,
            content_id=content_id,
            status=db_model.Nudges.Status.ACTIVE,
        )

        try:
            db.session.add(nudge_details)
            db.session.commit()
            message.append("Nudge scheduled successfully")
        except Exception as e:
            message.append(e)
    else:
        flash(error_list, "error")

    flash(message, "message")
    return flask.redirect("/schedule_nudge")


@app.route("/delete_campaign/<cp_id>")
@admin_permission.require(http_exception=403)
def delete_campaign(cp_id):
    db_model.Campaign.delete_campaign(cp_id)
    return flask.redirect("/todays_campaigns")


@app.route("/cancel_queued_campaigns")
@admin_permission.require(http_exception=403)
def cancel_queued_campaigns():
    queued_camps = db_model.Campaign.query.get_campaigns_with_status("queued")
    for c in queued_camps:
        # pass
        c.change_campaign_status("unscheduled")
    return flask.redirect("/todays_campaigns")


############################################## DATABASE #############################################################

##-------------------------------------------- CONTENT ------------------------------------------------------------##


@app.route("/content")
@admin_permission.require(http_exception=403)
def content_panel():
    try:
        current_page = request.args.get("page", 1, type=int)
        all_versions = db_model.ContentVersion.query.get_all_content_versions(
            current_page
        )
        languages = db_model.Language.query.get_all_languages()

        return flask.render_template(
            "home.html",
            active_panel="content",
            active_view="content",
            content_ver=all_versions,
            languages=languages,
        )
    except Exception as e:
        app_logger.error(f"Error fetching content versions: {e}")
        return f"Error fetching content versions: {e}", 500


@app.route("/add_content", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_content():
    if request.method == "POST":
        content = request.form["content"].split("\n")
        duration = request.form["duration"].split("\n")
        exotel_appid = request.form["appid"].split("\n")
        language = request.form["language"].split("\n")
        total_contents = len(content)
        if (
            len(duration) != total_contents
            or len(exotel_appid) != total_contents
            or len(language) != total_contents
        ):
            flash(
                "Values in all fields are required for each record. Please check if all the fields have equal number of data"
            )
        else:
            c_data = list(zip(content, duration, exotel_appid, language))
            errors = db_model.Content.add_new_contents(c_data)
            if not errors:
                flash("Successfully added all the contents")
            else:
                error_message = "Got error for contents: " + errors
                flash(error_message, "error")
        return render_template("partials/content_form.html")

    return render_template("partials/content_form.html")


@app.route("/get-versions", methods=["POST"])
@admin_permission.require(http_exception=403)
def get_versions():
    content_id = request.form["id"]
    content_versions = db_model.ContentVersion.query.get_content_version_by_content_id(
        content_id
    )
    versions = None
    message = "Existing Versions are : "
    for cv in content_versions:
        if versions:
            versions = (
                versions + "<br>" + str(cv.version) + "( " + cv.language.name + " )"
            )
        else:
            versions = "<br>" + str(cv.version) + "( " + cv.language.name + " )"
    message = message + versions
    return message


@app.route("/add-content-version", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_content_version():
    contents = db_model.Content.query.get_content_log()
    languages = db_model.Language.query.get_all_languages()

    if request.method == "POST":
        content = request.form["content"]
        duration = request.form["duration"]
        exotel_appid = request.form["appid"]
        language = request.form["language"]
        version = request.form["version"]
        c_data = [content, duration, exotel_appid, language, version]
        errors = db_model.ContentVersion.add_new_content_version(c_data)
        if not errors:
            flash("Successfully added all the contents")
        else:
            error_message = "Got error: " + errors
            flash(error_message, "error")
        return render_template(
            "partials/content_version_form.html", contents=contents, languages=languages
        )

    return render_template(
        "partials/content_version_form.html", contents=contents, languages=languages
    )


@app.route("/update-content", methods=["POST"])
@admin_permission.require(http_exception=403)
def update_content():
    if request.method == "POST":
        content_id = request.form["content_id"]
        duration = request.form["duration"]
        exotel_appid = request.form["appid"]
        language = request.form["language"]

        content_data = [content_id, duration, exotel_appid, language]
        db_model.ContentVersion.update_contents(content_data)
        return redirect("/content")

    return redirect("/content")


@app.route("/delete_content")
@admin_permission.require(http_exception=403)
def delete_content():
    db_helper.clear_content()
    return redirect("/content")


@app.route("/toggle_content_status", methods=["POST"])
@admin_permission.require(http_exception=403)
def toggle_content_status():
    content_version_id = request.form["id"]
    content_details = db_model.ContentVersion.query.get_content_version_by_id(
        content_version_id
    )
    if content_details.status == db_model.ContentVersion.Status.ACTIVE:
        content_details.status = db_model.ContentVersion.Status.INACTIVE
        db.session.commit()
    elif content_details.status == db_model.ContentVersion.Status.INACTIVE:
        content_details.status = db_model.ContentVersion.Status.ACTIVE
        db.session.commit()
    return jsonify(status=content_details.status, id=content_version_id)


##--------------------------------------------- SYSTEM CONFIGURATION -----------------------------------------------##


@app.route("/delete_config", methods=["DELETE"])
@admin_permission.require(http_exception=403)
def delete_system_config():
    config_id = request.args["id"]
    data = db_model.SystemConfiguration.query.get_config_by_id(config_id)
    if data:
        record_instance = db_model.SystemConfiguration()
        try:
            record_instance.delete_config(config_id)
            return {"success": True}
        except Exception:
            return {
                "error": "Internal server error while trying to process the request."
            }
    else:
        return {"error": f"Config Id: {config_id} was not found in the records."}


@app.route("/set_config", methods=["POST"])
@admin_permission.require(http_exception=403)
def set_system_config():
    request_data = json.loads(request.data)
    config_name = request_data["name"]
    config_value = request_data["value"]
    record_instance = db_model.SystemConfiguration()
    try:
        record_instance.set_config(config_name.upper(), config_value)
        current_user = db_model.AdminUser.query.filter(
            db_model.AdminUser.id == adminuser.get_current_adminuser_id()
        ).first()
        user_info = f"{current_user.__class__.__name__} {current_user.username}"
        if user_info:
            app_logger.warning(
                f"The system configuration {config_name} was set to {config_value} by the {user_info}"
            )
            if config_name == EXOTEL_CALLS_PERMITTED:
                response = slackutils.initiate_slack_alert_request(
                    current_user, config_name, config_value
                )
                if response.status_code == 200:
                    return {"success": True}
                return {
                    "error": f"Failed to initiate slack alert request. Status code: {response.status_code}"
                }
    except Exception as e:
        return {
            "error": f"Internal server error while trying to process the request. {e}"
        }

    return None


############################################### Pause Exotel Calls #############################################################


@app.route("/pause_exotel_calls", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def pause_exotel_calls():
    errors = []
    try:
        if request.method == "GET":
            exotel_calls_permitted = config_parser.SystemConfigurationUtil.parse_config(
                EXOTEL_CALLS_PERMITTED
            )

        if not exotel_calls_permitted or str(exotel_calls_permitted.value).upper() in [
            "NONE",
            "FALSE",
        ]:
            exotel_calls_permitted = False
        else:
            exotel_calls_permitted = True

            return render_template(
                "/partials/pause_exotel_calls.html",
                exotel_calls_permitted=exotel_calls_permitted,
            )
    except Exception as e:
        errors.append(f"Error pausing calls: {str(e)}")

    if errors:
        app_logger.error("Errors occurred while pausing calls: %s", ", ".join(errors))
    else:
        app_logger.info("Successfully paused Exotel calls.")

    return render_template(
        "/partials/pause_exotel_calls.html",
        exotel_calls_permitted=exotel_calls_permitted,
    )


##--------------------------------------------- NUDGE -------------------------------------------------------------##


@app.route("/nudge")
@admin_permission.require(http_exception=403)
def nudge_panel():
    nudges = db_model.Nudges.query.get_all_nudges()
    return flask.render_template(
        "home.html", active_panel="nudge", active_view="nudge", nudges=nudges
    )


@app.route("/toggle_status", methods=["POST"])
@admin_permission.require(http_exception=403)
def toggle_status():
    nudge_id = request.form["id"]
    nudge_details = db_model.Nudges.query.get_details_by_id(nudge_id)
    if nudge_details.status == db_model.Nudges.Status.ACTIVE:
        nudge_details.status = db_model.Nudges.Status.INACTIVE
        db.session.commit()
    elif nudge_details.status == db_model.Nudges.Status.INACTIVE:
        nudge_details.status = db_model.Nudges.Status.ACTIVE
        db.session.commit()
    return jsonify(status=nudge_details.status, id=nudge_id)


##---------------------------------------- IVR PROMPTS --------------------------------------------------------##


@app.route("/ivr-prompts")
@admin_permission.require(http_exception=403)
def ivr_prompts():
    current_page = request.args.get("page", 1, type=int)
    ivr_prompts = events_model.IVRPrompt.query.get_paginated_ivr_prompts(current_page)
    return flask.render_template(
        "home.html",
        active_panel="ivrprompt",
        active_view="ivrprompt",
        ivr_prompts=ivr_prompts,
    )


@app.route("/download-sample-sheet")
@admin_permission.require(http_exception=403)
def download_sample_sheet():
    si = StringIO()
    cw = csv.writer(si)
    cw.writerows([("id", "exotel_appid", "event", "keypress", "event_type", "status")])
    response = make_response(si.getvalue())
    response.headers[
        "Content-Disposition"
    ] = "attachment; filename=sample_ivrprompt.csv"
    response.headers["Content-type"] = "text/csv"
    return response


@app.route("/download-ivr-prompt")
@admin_permission.require(http_exception=403)
def download_ivr_prompt():
    ivr_prompts = events_model.IVRPrompt.query.get_all_ivr_prompts()
    si = StringIO()
    cw = csv.writer(si)
    istnow = get_current_utc_time() + timedelta(hours=5, minutes=30)
    cw.writerows([("id", "exotel_appid", "event", "keypress", "event_type", "status")])
    cw.writerows(
        [
            (r.id, r.exotel_appid, r.event, r.keypress, r.event_type, r.status)
            for r in ivr_prompts
        ]
    )
    response = make_response(si.getvalue())
    response.headers["Content-Disposition"] = (
        "attachment; filename=ivrprompt" + str(istnow) + ".csv"
    )
    response.headers["Content-type"] = "text/csv"
    return response


@app.route("/upload-prompts", methods=["POST"])
@admin_permission.require(http_exception=403)
def upload_prompts():
    uploaded_sheet = request.files["upload"]
    ivr_prompt_dict = {}
    ivr_prompts = events_model.IVRPrompt.query.get_all_ivr_prompts()

    for prompt in ivr_prompts:
        ivr_prompt_dict[prompt.id] = prompt

    if uploaded_sheet != "":
        csv_reader = csv.DictReader(codecs.iterdecode(uploaded_sheet.stream, "utf-8"))
        error_prompts = []
        for row in csv_reader:
            try:
                prompt_id = int(row["id"].strip()) if row["id"] != "" else None
                exotel_appid = (
                    int(row["exotel_appid"].strip())
                    if row["exotel_appid"] != ""
                    else None
                )
                event = row["event"].strip()
                keypress = (
                    int(row["keypress"].strip()) if row["keypress"] != "" else None
                )
                event_type = (
                    row["event_type"].strip() if row["event_type"] != "" else None
                )
                status = (
                    (row["status"].strip()).lower() if row["status"] != "" else None
                )
                if event != "":
                    if prompt_id is not None:
                        ivr_prompt = ivr_prompt_dict.get(prompt_id)
                        if ivr_prompt:
                            if (
                                ivr_prompt.exotel_appid != exotel_appid
                                or ivr_prompt.event != event
                                or ivr_prompt.keypress != keypress
                                or ivr_prompt.event_type != event_type
                                or ivr_prompt.status != status
                            ):
                                ivr_prompt.exotel_appid = exotel_appid
                                ivr_prompt.event = event
                                ivr_prompt.keypress = keypress
                                ivr_prompt.event_type = event_type
                                ivr_prompt.status = status
                                db.session.commit()
                    else:
                        prompt_rec = events_model.IVRPrompt(
                            event=event,
                            keypress=keypress,
                            exotel_appid=exotel_appid,
                            event_type=event_type,
                            status=status,
                        )
                        db.session.add(prompt_rec)
                        db.session.commit()
            except Exception as e:
                app_logger.error(
                    f"Exception occurred while uploading prompts. Error: {e}"
                )
                error_prompts.append(row["event"])
        if error_prompts:
            flash(error_prompts, "error")
    return redirect("/ivr-prompts")


@app.route("/add-ivr-prompts", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_ivr_prompts():
    if request.method == "POST":
        event = request.form["event"].split("\n")
        keypress = request.form["keypress"].split("\n")
        exotel_appid = request.form["appid"].split("\n")
        total_contents = len(event)
        if len(keypress) != total_contents or len(exotel_appid) != total_contents:
            flash(
                "Values in all fields are required for each record. Please check if all the fields have equal number of data"
            )
        else:
            ivr_data = list(zip(event, keypress, exotel_appid))
            errors = events_model.IVRPrompt.add_new_prompts(ivr_data)
            if not errors:
                flash("Successfully added all the IVR Prompts")
            else:
                error_message = "Got error for Prompts: " + errors
                flash(error_message, "error")
        return render_template("partials/ivr_prompt_form.html")

    return render_template("partials/ivr_prompt_form.html")


@app.route("/update-prompts", methods=["POST"])
@admin_permission.require(http_exception=403)
def update_prompts():
    if request.method == "POST":
        prompt_id = request.form["prompt_id"]
        event = request.form["event"]
        exotel_appid = request.form["appid"]
        event_type = request.form["event_type"]
        keypress = request.form["keypress"]

        prompt_data = [prompt_id, exotel_appid, event_type, keypress, event]
        events_model.IVRPrompt.update_prompts(prompt_data)
        return redirect("/ivr-prompts")

    return redirect("/ivr-prompts")


@app.route("/toggle_prompt_status", methods=["POST"])
@admin_permission.require(http_exception=403)
def toggle_prompt_status():
    ivr_prompt_id = request.form["id"]
    prompt_details = events_model.IVRPrompt.query.get_ivr_prompt_by_id(ivr_prompt_id)
    if prompt_details.status == events_model.IVRPrompt.Status.ACTIVE:
        prompt_details.status = events_model.IVRPrompt.Status.INACTIVE
        db.session.commit()
    else:
        prompt_details.status = events_model.IVRPrompt.Status.ACTIVE
        db.session.commit()
    return jsonify(status=prompt_details.status, id=ivr_prompt_id)


##------------------------------------- IVR PROMPTS MAPPING-----------------------------------------------------##


@app.route("/ivr-prompt-mapping")
@admin_permission.require(http_exception=403)
def ivr_prompt_mapping():
    current_page = request.args.get("page", 1, type=int)
    ivr_prompt_mapping = db_model.IvrPromptMapping.query.get_all_prompt_mapping(
        current_page
    )
    return flask.render_template(
        "home.html",
        active_panel="ivrpromptmapping",
        active_view="ivrpromptmapping",
        ivr_prompt_mapping=ivr_prompt_mapping,
    )


@app.route("/add-ivr-prompt-mapping", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_prompt_mapping():
    if request.method == "GET":
        prompts = events_model.IVRPrompt.query.get_all_ivr_prompts()
        return render_template(
            "partials/prompt_mapping_form.html",
            form_action="/add-ivr-prompt-mapping",
            prompts=prompts,
        )

    errors = None
    event_id = request.form["event"]
    mapped_table = request.form["table"]
    mapped_column = request.form["column"]
    value = request.form["value"]
    find_table = f"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '{mapped_table}')"
    cursor.execute(find_table)
    table_exists = cursor.fetchone()[0]
    find_column = f"""SELECT EXISTS (SELECT FROM information_schema.columns
    WHERE table_name = '{mapped_table}' and column_name = '{mapped_column}')"""
    cursor.execute(find_column)
    column_exists = cursor.fetchone()[0]
    if table_exists is False:
        errors = "Table " + mapped_table + " does not exists"
    elif column_exists is False:
        errors = (
            "Column " + mapped_column + " does not exists in the table " + mapped_table
        )
    else:
        add_mapping = db_model.IvrPromptMapping(
            ivr_prompt_id=int(event_id),
            mapped_table_name=mapped_table,
            mapped_table_column_name=mapped_column,
            value=value,
            status=db_model.IvrPromptMapping.Status.ACTIVE,
        )
        db.session.add(add_mapping)
        db.session.commit()

    if not errors:
        flash("Successfully added all the IVR Prompts")
    else:
        error_message = "Got error while adding prompts"
        flash(error_message, "error")
        flash(errors, "error")
    return flask.redirect("/add-ivr-prompt-mapping")


@app.route("/update-mapping", methods=["POST"])
@admin_permission.require(http_exception=403)
def update_mapping():
    if request.method == "POST":
        mapping_id = request.form["mapping_id"]
        mapped_table = request.form["table"]
        mapped_column = request.form["column"]
        value = request.form["value"]
        errors = None
        find_table = f"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = '{mapped_table}')"
        cursor.execute(find_table)
        table_exists = cursor.fetchone()[0]
        find_column = f"SELECT EXISTS (SELECT FROM information_schema.columns WHERE table_name = '{mapped_table}' and column_name = '{mapped_column}')"
        cursor.execute(find_column)
        column_exists = cursor.fetchone()[0]
        if table_exists is False:
            errors = "Table " + mapped_table + " does not exists"
        elif column_exists is False:
            errors = (
                "Column "
                + mapped_column
                + " does not exists in the table "
                + mapped_table
            )
        else:
            prompt_data = [mapping_id, mapped_table, mapped_column, value]
            db_model.IvrPromptMapping.update_prompts(prompt_data)
        if not errors:
            flash("Successfully updated the IVR Prompts Mapping")
        else:
            error_message = "Got error while updating prompts"
            flash(error_message, "error")
            flash(errors, "error")
        return flask.redirect("/ivr-prompt-mapping")

    return flask.redirect("/ivr-prompt-mapping")


@app.route("/toggle_mapping_status", methods=["POST"])
@admin_permission.require(http_exception=403)
def toggle_mapping_status():
    ivr_prompt_mapping_id = request.form["id"]
    mapping_details = db_model.IvrPromptMapping.query.get_by_id(ivr_prompt_mapping_id)
    if mapping_details.status == db_model.IvrPromptMapping.Status.ACTIVE:
        mapping_details.status = db_model.IvrPromptMapping.Status.INACTIVE
        db.session.commit()
    else:
        mapping_details.status = db_model.IvrPromptMapping.Status.ACTIVE
        db.session.commit()
    return jsonify(status=mapping_details.status, id=ivr_prompt_mapping_id)


##--------------------------------------------- USERS -------------------------------------------------------------##


@app.route("/users")
@admin_permission.require(http_exception=403)
def users_panel():
    page = request.args.get("page", 1, type=int)
    per_page = 30
    paginated_experiences = db_model.Experience.query.get_active_experiences(
        page=page, per_page=per_page
    )
    return flask.render_template(
        "home.html",
        active_panel="users",
        active_view="users",
        experiences=paginated_experiences.items,
        pagination=paginated_experiences,
    )


@app.route("/add_users_and_upload_consent", methods=["GET", "POST"])
@admin_onboarder_permission.union(program_associate_permission).require(
    http_exception=403
)
def add_users_and_upload_consent():
    if request.method == "GET":
        return handle_add_user_get_request()

    return handle_combined_post()


def handle_combined_post():
    raw_phones = (request.form.get("phones") or "").strip()
    has_users = bool(raw_phones)

    photo = request.files.get("photo")
    consent_name = (request.form.get("consent_name") or "").strip()
    has_photo = bool(photo and getattr(photo, "filename", "").strip())
    has_consent = bool(consent_name and has_photo)

    if not has_users and not has_consent:
        flash(
            "कृपया देखभालकर्ता जोड़ने के लिए फ़ोन नंबर दें या सहमति के लिए नाम व फोटो अपलोड करें।",
            "error",
        )
        return redirect(url_for("add_users_and_upload_consent"))

    if has_consent:
        upload_consent(photo, consent_name)

    if has_users:
        return handle_add_user_post_request()

    return redirect(url_for("add_users_and_upload_consent"))


def handle_add_user_get_request():
    """Handles the GET request and loads the user form."""
    experience_types = db_model.Experience.Type.CHOICE
    timeranges = Timecategory.TimeRange.HINDI_RANGE
    hindi_block_names = (
        db_model.DistrictBlockMapping.query.get_all_hindi_block_names() or []
    )
    programs = db_model.Program.Name.HINDI_NAME

    if not hindi_block_names:
        app.logger.error(
            "No blocks found in District Block Mapping while adding new user through UI"
        )
        flash(
            "कृपया ब्लॉक जोड़ने के लिए इंजीनियरिंग टीम से संपर्क करें।",
            "error",
        )

    return render_template(
        "partials/user_form.html",
        experience_types=experience_types,
        timeranges=timeranges,
        blocks=hindi_block_names,
        programs=programs,
    )


def handle_add_user_post_request():
    """Handles the POST request, validates input, and processes users."""
    current_user = db_model.AdminUser.query.get(adminuser.get_current_adminuser_id())
    user_data = validate_user_input()
    if user_data is None:
        return redirect("/add_users_and_upload_consent")

    uphones, experience_type, hindi_block, timerange, uprogram = user_data
    if not uphones:
        flash("कृपया एक फ़ोन नंबर प्रदान करें।", "error")
        return redirect("/add_users_and_upload_consent")
    if not experience_type:
        flash("कृपया अनुभव प्रकार चुनें।", "error")
        return redirect("/add_users_and_upload_consent")
    if not hindi_block:
        flash("कृपया एक ब्लॉक चुनें।", "error")
        return redirect("/add_users_and_upload_consent")
    if not timerange:
        flash("कृपया एक समय श्रेणी चुनें।", "error")
        return redirect("/add_users_and_upload_consent")
    if not uprogram:
        flash("कृपया एक कार्यक्रम चुनें।", "error")
        return redirect("/add_users_and_upload_consent")

    district_block_mapping = (
        db_model.DistrictBlockMapping.query.get_block_by_hindi_name(hindi_block)
    )

    state = district_block_mapping.state if district_block_mapping else None
    district = district_block_mapping.district if district_block_mapping else None
    block = district_block_mapping.block if district_block_mapping else None

    partners = []

    if district and state:
        partners = db_model.Partner.query.get_partner_by_state_and_district(
            state, district
        )

    if not partners and district:
        partners = db_model.Partner.query.get_partner_by_district(district)

    if not partners and state:
        partners = db_model.Partner.query.get_partner_by_state(state)

    if not partners:
        flash(
            f"कृपया {hindi_block} ब्लॉक के लिए दोस्त टोल फ्री नंबर जोड़ने के लिए इंजीनियरिंग टीम से संपर्क करें",
            "error",
        )

    for partner in partners:
        provider = db_model.ProviderNumber.query.get_provider_number_by_partner_id(
            partner.id
        )
        if provider:
            break

    existing_experience_phones, new_users, errors = verify_and_process_users_data(
        uphones, experience_type, provider, block, timerange, uprogram, current_user
    )

    flash_messages(existing_experience_phones, new_users, errors)

    return redirect("/add_users_and_upload_consent")


def validate_user_input():
    """Validates form input and returns user data if valid, otherwise flashes an error."""

    # --- Validate phone numbers ---
    raw_phones = request.form.get("phones", "").strip()
    if not raw_phones:
        flash("कृपया कम से कम एक फ़ोन नंबर दर्ज करें।", "error")
        return None

    uphones = []
    for phone in raw_phones.split("\n"):
        phone = phone.strip().replace(" ", "")
        if not phone:
            continue
        # Keep only last 10 digits (for consistency)
        digits_only = "".join([ch for ch in phone if ch.isdigit()])
        if len(digits_only) < 10:
            flash(f"अमान्य फ़ोन नंबर: {phone}", "error")
            return None
        uphones.append(digits_only[-10:])

    if not uphones:
        flash("मान्य फ़ोन नंबर आवश्यक हैं।", "error")
        return None

    # --- Validate experience type ---
    experience_type = request.form.get("experience_type")
    if not experience_type:
        flash("कृपया अनुभव प्रकार चुनें।", "error")
        return None

    # --- Validate block ---
    block = request.form.get("blocks")
    if not block:
        flash("कृपया ब्लॉक चुनें।", "error")
        return None

    # --- Validate timerange ---
    timerange = request.form.get("timeranges")
    if not timerange:
        flash("कृपया समय श्रेणी चुनें।", "error")
        return None

    if timerange == Timecategory.TimeRange.MORNING_TIME_RANGE_HINDI:
        timerange = Timecategory.TimeRange.MORNING_TIME_RANGE
    elif timerange == Timecategory.TimeRange.AFTERNOON_TIME_RANGE_HINDI:
        timerange = Timecategory.TimeRange.AFTERNOON_TIME_RANGE
    elif timerange == Timecategory.TimeRange.EVENING_TIME_RANGE_HINDI:
        timerange = Timecategory.TimeRange.EVENING_TIME_RANGE

    # --- Validate program ---
    uprogram = request.form.get("program")
    if not uprogram:
        flash("कृपया कार्यक्रम चुनें।", "error")
        return None

    if uprogram == db_model.Program.Name.UK_USN_T_6_HINDI:
        uprogram = db_model.Program.Name.UK_USN_T_6
    elif uprogram == db_model.Program.Name.UK_USN_B_3_HINDI:
        uprogram = db_model.Program.Name.UK_USN_B_3

    return uphones, experience_type, block, timerange, uprogram


def verify_and_process_users_data(
    uphones, experience_type, provider, block, timerange, uprogram, current_user
):
    """Verify and Processes users data: checks for existing experience and adds new users."""
    existing_experience_phones = []
    new_users = []
    errors = []

    for phone in uphones:
        phone = phone.strip()
        if not phone:
            continue

        experience = db_model.Experience.query.find_experience_with_phone(
            replace_chars(phone)
        )

        if experience:
            existing_experience_phones.append(phone)
        else:
            user_errors, _, _, _, _ = db_helper.add_non_mvp_users(
                (phone, uprogram, block, timerange),
                experience_type,
                provider,
                current_user,
            )
            if user_errors:
                errors.extend(user_errors)
            else:
                new_users.append(phone)

    return existing_experience_phones, new_users, errors


def flash_messages(existing_experience_phones, new_users, errors):
    """Displays flash messages based on user processing results."""
    if existing_experience_phones:
        flash(
            f"{len(existing_experience_phones)} देखभालकर्ताओं के पास पहले से ही सक्रिय अनुभव है: "
            f"{', '.join(existing_experience_phones)}",
            "info",
        )

    if new_users:
        flash(
            f"{len(new_users)} नए देखभालकर्ता सफलतापूर्वक जोड़े गए: {', '.join(new_users)}",
            "success",
        )

    if errors:
        flash(
            "अपलोड करते समय कुछ गलतियाँ हुईं। कृपया इंजीनियरिंग टीम से संपर्क करें।",
            "error",
        )
        for error in errors:
            flash(error, "error")


def upload_consent(photo, consent_name):
    filename = replace_chars(consent_name) + "_" + photo.filename
    try:
        drive_file = upload_file_to_drive(
            photo.stream, filename, photo.mimetype, folder_id=DRIVE_FOLDER_ID
        )
        file_id = drive_file["id"]
        drive_link = drive_file["view_link"]

        new_consent = db_model.Consent(
            name=consent_name,
            drive_url=drive_link,
            image_url=f"https://drive.google.com/uc?id={file_id}",
            uploaded_by=adminuser.get_current_adminuser_id(),
            uploaded_on=datetime.utcnow(),
        )

        db.session.add(new_consent)
        db.session.commit()
        flash(f"Consent '{consent_name}' uploaded successfully!", "success")
    except Exception as e:
        db.session.rollback()
        flash(
            f"Error uploading file to Google Drive. Please contact support. Error: {str(e)}",
            "error",
        )
    return redirect(url_for("add_users_and_upload_consent"))


##------------------------------------------- PARTNERS -----------------------------------------------------------##


@app.route("/partners")
@admin_permission.require(http_exception=403)
def partners_panel():
    current_page = request.args.get("page", 1, type=int)
    partners = db_model.Partner.query.get_partners_log(current_page)
    return flask.render_template(
        "home.html",
        active_panel="partners",
        active_view="partners",
        partners=partners,
    )


@app.route("/add_partner", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_partner():
    if request.method == "GET":
        return render_template(
            "partials/partner_form.html",
        )

    p_names = request.form["name"].split("\n")
    p_area = request.form["area"].split("\n")
    p_city = request.form["city"].split("\n")
    p_channel = request.form["channel_name"].split("\n")
    p_email = request.form["email"].split("\n")
    partners = list(zip(p_names, p_area, p_city, p_channel, p_email))
    db_helper.add_to_partner_table(partners)
    return redirect("/partners")


@app.route("/delete_partner/<partner_id>")
@admin_permission.require(http_exception=403)
def delete_partner(partner_id):
    partner_id = int(partner_id)
    db_model.Partner.delete_partner(partner_id)

    return flask.redirect("/partners")


##------------------------------------------- CHANNELS -----------------------------------------------------------##


@app.route("/channels")
@admin_permission.require(http_exception=403)
def channels_panel():
    channels = db_model.Channel.query.get_channel_log()
    return flask.render_template(
        "home.html", active_panel="channels", active_view="channels", channels=channels
    )


@app.route("/add_channels", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_channel():
    if request.method == "GET":
        return render_template("partials/channel_form.html")

    channels = request.form["name"].split("\n")
    db_helper.add_to_channel_table(channels)
    return redirect("/channels")


##------------------------------------------- PROGRAM -----------------------------------------------------------##
@app.route("/programs")
@admin_permission.require(http_exception=403)
def programs_panel():
    current_page = request.args.get("page", 1, type=int)
    programs = db_model.Program.query.get_paginated_programs_log(current_page)

    return flask.render_template(
        "home.html",
        active_panel="programs",
        active_view="programs",
        programs=programs,
    )


@app.route("/program_details/<program_id>")
@admin_permission.require(http_exception=403)
def program_details(program_id):
    program = db_model.Program.query.get_program_details(program_id)
    if program:
        return flask.render_template(
            "home.html",
            active_panel="program_details",
            active_view="program_details",
            program=program,
        )
    return redirect("/programs")


@app.route("/programseq/<program_id>", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def programseq_panel(program_id):
    if request.method == "GET":
        program = db_model.Program.query.get_program_details(program_id)
        return render_template("partials/programseq_form.html", program=program)

    seq_idx = request.form["seq_idx"].split("\n")
    content = request.form["content"].split("\n")
    week = request.form["week"].split("\n")
    day = request.form["day"].split("\n")
    pdata = list(zip(seq_idx, content, week, day))
    db_helper.add_to_program(program_id, pdata)
    return redirect("/programs")


@app.route("/add_program", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_program():
    form = AddProgramForm(request.form)
    if form.validate_on_submit():
        program_obj = db_model.Program(
            name=form.name.data,
            version=form.version.data,
            start_date=form.start_date.data,
            end_date=form.end_date.data,
            status=form.status.data,
            type=form.program_type.data,
            hindi_name=form.hindi_name.data,
            description=form.description.data,
        )
        db.session.add(program_obj)
        db.session.commit()
        flash("Successfully added the program")
        return redirect("/programs")

    return render_template("partials/program_form.html", form=form)


@app.route("/schedule_program", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_pause_log():
    if request.method == "GET":
        programs = db_model.Program.query.get_programs_log()
        return render_template(
            "partials/experiencepauselog_form.html", programs=programs
        )

    phones = request.form["phone"].split("\n")
    start_dates = request.form["pause_start_date"].split("\n")
    next_program = request.form["next_program"]
    pause_log_data = list(zip(phones, start_dates))
    no_experience, pending_pause_log = db_helper.add_experience_pause_log(
        pause_log_data, next_program
    )
    for phone in no_experience:
        flash(user_does_not_have_active_experience(phone), "error")
    for phone in pending_pause_log:
        flash(user_pending_pause_log(phone), "error")
    flash("Program for users without errors has been queued succesfully", "error")
    return redirect("/schedule_program")


################################################ CALLS ###############################################################


@app.route("/calls", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def calls_panel():
    if request.method == "GET":
        return flask.render_template(
            "home.html", active_panel="calls", active_view="calls"
        )

    exo_phone = request.form["exo_phone"]
    exo_appid = request.form["exo_appid"]
    phones = request.form["phones"].split("\n")
    for p in phones:
        exotel_helper.make_call(
            p, exo_appid, trial=3, ignore_check=True, exo_phone=exo_phone
        )
    return redirect("/")


@app.route("/engagement_report")
@admin_permission.require(http_exception=403)
def engagement_report():
    return flask.redirect("/home")


@app.route("/tcr", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def tcr_panel():
    if request.method == "GET":
        timecategories = db_model.Timecategory.query.get_timecategory_log()
        return flask.render_template(
            "partials/time_change_panel.html",
            timecategories=timecategories,
            active_panel="time_change",
            active_view="time_change",
        )

    phone = request.form["phone"]
    timecategory = request.form["time_category"]
    if db_model.ChangeReqs.create_change_request(phone, timecategory):
        flash("Successfully created a request. The changes would happen by tomorrow")
    else:
        flash("There was some errors while updating the data.", "error")
    return redirect("/tcr")


################################################ CALLS ###############################################################

##---------------------------------------- MISSED CALL --------------------------------------------------------##


# The following view only deals with the missed calls.
@app.route("/exocallin", methods=["GET", "POST"])
def exocallin():
    try:
        if request.method == "GET":
            user_phone = request.args.get("From")
            app_logger.info(
                f"Missed call Handling: Received a missed call from {user_phone}"
            )
            if "From" in list(request.args.keys()) and "To" in list(
                request.args.keys()
            ):
                MissedCallService().handle_missed_call(request.args)
        return json.dumps({"success": True}), 200, {"ContentType": "application/json"}
    except Exception as e:
        app_logger.error(f"Exception on exocallin function. Error: {e}")
        app_logger.debug(traceback.format_exc())


@app.route("/ciffcallback", methods=["GET", "POST"])
def ciffcallback():
    content_appids = [152400, 153061, 151514, 151512]
    exo_phone = "01140844944"
    if request.method == "GET":
        app_logger.info("Received a missed call from %s: ", request.args.get("From"))
        if "From" in list(request.args.keys()) and "To" in list(request.args.keys()):
            user_number = request.args.get("From")
            if user_number:
                user_number = user_number[1:]
                exotel_appid = content_appids[randint(0, 3)]
                exotel_helper.make_call(
                    phone=user_number,
                    exo_appid=exotel_appid,
                    ignore_check=True,
                    exo_phone=exo_phone,
                )
    return redirect("/")


@app.route("/rapidprotest", methods=["GET"])
def rapidprotest():
    to_number = request.args.get("to")
    app_id = request.args.get("appid")
    if not to_number or not app_id:
        return (
            json.dumps(
                {"RestException": {"Status": 400, "Message": "Invalid Call Parameters"}}
            ),
            400,
            {"ContentType": "application/json"},
        )

    return (
        json.dumps(
            {
                "Call": {
                    "Sid": "mb33g922c5e6f79exedxz0f4y19714a1",
                    "ParentCallSid": None,
                    "DateCreated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "DateUpdated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "AccountSid": "dosteducation",
                    "To": "01140844787",
                    "From": to_number,
                    "PhoneNumberSid": "01140844787",
                    "Status": "in-progress",
                    "StartTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "EndTime": None,
                    "Duration": None,
                    "Price": None,
                    "Direction": "outbound-api",
                    "AnsweredBy": None,
                    "ForwardedFrom": None,
                    "CallerName": None,
                    "Uri": "/v1/Accounts/dosteducation/Calls/xxxxx.json",
                    "RecordingUrl": None,
                }
            }
        ),
        200,
        {"ContentType": "application/json"},
    )


@app.route("/confirmation_call", methods=["GET", "POST"])
def confirmation_call():
    if request.method == "GET":
        phone = request.args.get("From")
        user_number = phone[1:]
        experience = db_model.Experience.query.find_experience_with_phone_status(
            user_number,
            [
                db_model.Experience.Status.SIGNUP,
                db_model.Experience.Status.SIGNUP_UNCONFIRMED,
            ],
        )
        if experience:
            app_logger.info("Updating status for user with phone %s", user_number)
            try:
                touchtone_value = int(request.args.get("digits", 0).replace('"', ""))
            except Exception as e:
                touchtone_value = -1
                app_logger.error(
                    f"Exception occurred while scheduling confirmation calls. Error: {e}"
                )
            if touchtone_value == 1:
                experience.status = db_model.Experience.Status.SIGNUP_CONFIRMED
            elif touchtone_value == 9:
                experience.status = db_model.Experience.Status.SIGNUP_REJECTED
            else:
                if experience.user.partner_id != db_model.Partner.query.get_by_name(
                    "callin"
                ):
                    experience.status = db_model.Experience.Status.SIGNUP_UNCONFIRMED
            db.session.commit()
        else:
            app_logger.info("No user found for the number %s", user_number)
        return json.dumps({"success": True}), 200, {"ContentType": "application/json"}

    return None


@app.route("/autocallciff/<appid>/<trial>", methods=["GET", "POST"])
def autocallciff(app_id, trial):
    return


@app.route("/livecall/<campaign_id>", methods=["GET", "POST"])
def livecall(campaign_id):
    if request.method == "POST":
        app_logger.info("Call attempt status: %s", request.form["Status"])
        campaign = db_model.Campaign.query.filter(
            db_model.Campaign.id == campaign_id
        ).first()
        if campaign and campaign.status != db_model.Campaign.Status.COMPLETED:
            status = request.form["Status"]
            call_sid = request.form["CallSid"]
            app_logger.info(
                f"Updating status of live call campaign with id {campaign_id} to {status}"
            )
            campaign.update_campaign_after_callback(
                request.form["Status"], request.form["CallSid"]
            )
            campaign_data = {
                "experience_id": campaign.experience_id,
                "scheduled_by": campaign.scheduled_by,
                "exotel_status": request.form["Status"],
            }
            PreCalculatedCampaignService().update_pre_calculated_user_campaign_data(
                campaign_data
            )

        else:
            app_logger.warning(
                f"No incomplete campaign with id {campaign_id} found for the callsid {call_sid}"
            )
        return flask.render_template("partials/campaign_form.html")

    return None


##------------------------------------- TOUCHTONE CALLBACK -----------------------------------------------------##


@app.route("/missedcallresponse", methods=["GET", "POST"])
def missedcallresponse():
    if request.method == "GET":
        user_number = request.args.get("From")
        user_number = user_number[1:]
        experience = db_model.Experience.query.find_experience_with_phone(user_number)

        if experience:
            touchtone_value = int(request.args.get("digits", 0).replace('"', ""))
            db_model.Experience.handle_callin_feedback(experience, touchtone_value)
        else:
            app_logger.warning("No campaign found for the phone %s", user_number)
        return flask.render_template("partials/campaign_form.html")

    return None


##--------------------------------------- STREAKS CALLBACK -------------------------------------------------------##


@app.route("/branch_streaks_response", methods=["GET", "POST"])
def streaksresponse():
    if request.method == "GET":
        user_number = request.args.get("From")
        user_number = user_number[1:]
        experience = db_model.Experience.query.find_experience_with_phone_status(
            user_number, db_model.Experience.Status.ACTIVE
        )
        if experience:
            touchtone_value = int(request.args.get("digits", 0).replace('"', ""))
            if touchtone_value == 1:
                program_id = db_model.Program.query.find_program_id_with_name("STREAKS")
                if program_id != -1:
                    if not db_model.ExperiencePauseLog.query.pending_pause_log_exists(
                        experience.id
                    ):
                        # check to see if the pause log doesn't exist already
                        program_len = db_model.Programseq.query.get_program_len(
                            program_id
                        )
                        today = get_current_isttime().date()
                        if (
                            experience.start_date.weekday()
                            == db_model.Experience.Cohort.WEEKEND
                        ):
                            # wednesday as the next pause start date
                            pause_start_date = today + timedelta(
                                days=-today.weekday() + 2, weeks=1
                            )
                            pause_end_date = pause_start_date + timedelta(
                                days=1, weeks=program_len
                            )
                        else:
                            # sunday as the next pause start date
                            pause_start_date = today + timedelta(
                                days=-today.weekday() - 1, weeks=1
                            )
                            pause_end_date = pause_start_date + timedelta(
                                days=1, weeks=program_len
                            )
                        app_logger.info(
                            "Creating new pause log for the phone %s", user_number
                        )
                        pause_log = db_model.ExperiencePauseLog(
                            experience_id=experience.id,
                            pause_start_date=pause_start_date,
                            pause_end_date=pause_end_date,
                            next_program_id=program_id,
                            status=db_model.ExperiencePauseLog.Status.PENDING,
                        )
                        db.session.add(pause_log)
                        db.session.commit()
        else:
            app_logger.error("No experience found for the phone %s", user_number)
        return flask.render_template("partials/campaign_form.html")
    return None


##------------------------------------- ADD NEW PROVIDER NUMBER -----------------------------------------------------##


@app.route("/add_provider_number", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_provider_number():
    if request.method == "GET":
        return render_template("partials/providernumber_form.html")

    if request.method == "POST":
        app_logger.info("Adding provider number with data %s", request.form)
        phone = request.form["phone"].split("\n")
        name = request.form["name"].split("\n")
        exotel_appid = request.form["exotel_appid"].split("\n")
        language = request.form["language"].split("\n")
        partner = request.form["partner"].split("\n")
        udata = list(zip(phone, name, exotel_appid, language, partner))
        errors = db_helper.add_provider_number(udata)

        if not errors:
            flash("Successfully added all the data")
        else:
            flash("There were following errors while uploading: ", "error")
            for error in errors:
                app_logger.error("Error while adding provider number: %s", error)
                flash(error, "error")
            flash("Please correct the data and add again.")

        return redirect("/add_provider_number")

    return None


##---------------------------------------- LIVE CALL --------------------------------------------------------------##
@app.route("/live_call", methods=["GET", "POST"])
@admin_surveyor_permission.union(program_associate_permission).require(
    http_exception=403
)
def live_call():
    if request.method == "GET":
        provider_numbers = db_model.ProviderNumber.query.get_live_call_provider_number()
        user_phone = request.args.get("user_phone", "")
        return render_template(
            "partials/live_call_form.html",
            exotel_phones=provider_numbers,
            agent_phone=current_user.phone,
            user_phone=user_phone,
            purposes=db_model.Campaign.LiveCallPurpose.CHOICE_ENGLISH,
        )

    app_logger.info("Making live call with data: %s", request.form)
    user_phone = request.form["user_phone"]
    agent_phone = request.form["agent_phone"]
    exotel_phone = request.form["exotel_phone"]
    purpose = request.form.get("purpose", "LC")
    user_experience = db_model.Experience.query.find_experience_with_phone(user_phone)
    campaign = db_model.Campaign(
        name=purpose,
        experience_id=user_experience.id if user_experience else None,
        deploy_datetime=get_current_isttime(),
        status=db_model.Campaign.Status.SCHEDULED,
        scheduled_by=db_model.Campaign.ScheduledBy.LIVE_CALL,
        user_id=user_experience.user_id if user_experience else None,
        provider_number=exotel_phone,
        user_number=user_phone,
        agent_number=agent_phone,
    )
    db.session.add(campaign)
    db.session.commit()
    status, error_message = exotel_helper.make_live_call(
        user_phone, agent_phone, exotel_phone, campaign
    )
    if status:
        return render_template(
            "partials/live_call_form.html",
            success=True,
            user_phone=user_phone,
            exotel_phones=db_model.ProviderNumber.query.get_live_call_provider_number(),
            agent_phone=current_user.phone,
            purposes=db_model.Campaign.LiveCallPurpose.CHOICE_ENGLISH,
        )

    return render_template(
        "partials/live_call_form.html",
        error=error_message,
        user_phone=user_phone,
        exotel_phones=db_model.ProviderNumber.query.get_live_call_provider_number(),
        agent_phone=current_user.phone,
        purposes=db_model.Campaign.LiveCallPurpose.CHOICE_ENGLISH,
    )


@app.route("/user_check", methods=["GET", "POST"])
def user_check():
    if request.method == "GET":
        return render_template("partials/user_check.html")

    user_phone = request.form["user_phone"]
    user_experience = db_model.Experience.query.find_oldest_experience_with_phone(
        user_phone
    )
    app_logger.error(
        f"Error occurred while adding user from dostadmin having phone number {user_phone}."
    )
    user_exists = False
    if not user_experience:
        return render_template(
            "partials/user_check_result.html",
            user_exists=user_exists,
            user_phone=user_phone,
        )

    user_exists = True
    return render_template(
        "partials/user_check_result.html",
        user_exists=user_exists,
        start_date=user_experience.start_date,
        user_phone=user_phone,
    )


@app.route("/feedback_call", methods=["GET", "POST"])
@login_required
def feedback_call():
    if request.method == "GET":
        user_phone = request.args.get("user_phone", "")
        return render_template(
            "partials/feedback_call.html",
            user_phone=user_phone,
        )

    app_logger.info("Making Feedback call with data:  %s", request.form)
    feedback_call_appid = app.config["FEEDBACK_EXO_APPID"]
    agent_phone = current_user.phone
    exotel_phone = db_model.ProviderNumber.query.get_live_call_provider_number()[
        0
    ].phone
    user_phone = request.form["user_phone"]
    user_experience = db_model.Experience.query.find_experience_with_phone(user_phone)
    campaign = db_model.Campaign(
        name="FB" + str(user_experience.user_id) if user_experience else "FB",
        experience_id=user_experience.id if user_experience else None,
        deploy_datetime=get_current_isttime(),
        status=db_model.Campaign.Status.SCHEDULED,
        scheduled_by=db_model.Campaign.ScheduledBy.FEEDBACK_CALL,
        user_id=user_experience.user_id if user_experience else None,
        provider_number=exotel_phone,
        user_number=user_phone,
        agent_number=agent_phone,
    )
    db.session.add(campaign)
    db.session.commit()
    exotel_helper.schedule_feedback_call(
        user_phone, exotel_phone, feedback_call_appid, campaign
    )
    flash("Successfully scheduled the feedback call to " + user_phone)
    return redirect("/feedback_call")


@app.route("/champion_call", methods=["GET", "POST"])
@admin_champion_persmission.require(http_exception=403)
def champion_call():
    if request.method == "GET":
        user_phone = request.args.get("user_phone", "")
        purpose = request.args.get("purpose", "CC")
        if user_phone:
            agent_phone = current_user.phone
            exotel_phone = (
                db_model.ProviderNumber.query.get_live_call_provider_number()[0].phone
            )
            user_experience = db_model.Experience.query.find_experience_with_phone(
                user_phone
            )
            app_logger.info("Live call data: %s", request.args)
            campaign = db_model.Campaign(
                name=purpose,
                experience_id=user_experience.id if user_experience else None,
                deploy_datetime=get_current_isttime(),
                status=db_model.Campaign.Status.SCHEDULED,
                scheduled_by=db_model.Campaign.ScheduledBy.CHAMPION_CALL,
                user_id=user_experience.user_id if user_experience else None,
                provider_number=exotel_phone,
                user_number=user_phone,
                agent_number=agent_phone,
            )
            db.session.add(campaign)
            db.session.commit()
            status, error_message = exotel_helper.make_live_call(
                user_phone, agent_phone, exotel_phone, campaign
            )
            if status:
                return render_template(
                    "partials/champion_call.html",
                    success=True,
                    user_phone=user_phone,
                    exotel_phone=exotel_phone,
                )

            return render_template(
                "partials/champion_call.html",
                error=error_message,
                user_phone=user_phone,
                exotel_phone=exotel_phone,
            )

    return None


@app.route("/champion_call_user", methods=["GET", "POST"])
@admin_champion_persmission.require(http_exception=403)
def champion_call_user():
    if request.method == "GET":
        provider_number = db_model.ProviderNumber.query.get_live_call_provider_number()[
            0
        ].phone
        return render_template(
            "partials/champion_call_user_form.html",
            exotel_phone=provider_number,
            purposes=db_model.Campaign.LiveCallPurpose.CHOICE_HINDI,
        )

    app_logger.info("Champion call data: %s", request.form)
    user_phone = request.form["user_phone"]
    agent_phone = request.form["agent_phone"]
    exotel_phone = request.form["exotel_phone"]
    purpose = request.form.get("purpose", "CC")
    user_experience = db_model.Experience.query.find_experience_with_phone(user_phone)
    campaign = db_model.Campaign(
        name=purpose,
        experience_id=user_experience.id if user_experience else None,
        deploy_datetime=get_current_isttime(),
        status=db_model.Campaign.Status.SCHEDULED,
        scheduled_by=db_model.Campaign.ScheduledBy.CHAMPION_CALL,
        user_id=user_experience.user_id if user_experience else None,
        provider_number=exotel_phone,
        user_number=user_phone,
        agent_number=agent_phone,
    )
    db.session.add(campaign)
    db.session.commit()
    status, error_message = exotel_helper.make_live_call(
        user_phone, agent_phone, exotel_phone, campaign
    )
    if status:
        return render_template(
            "partials/champion_call_user_form.html",
            success=True,
            user_phone=user_phone,
            exotel_phone=exotel_phone,
            purposes=db_model.Campaign.LiveCallPurpose.CHOICE_HINDI,
        )

    return render_template(
        "partials/champion_call_user_form.html",
        error=error_message,
        user_phone=user_phone,
        exotel_phone=exotel_phone,
        purposes=db_model.Campaign.LiveCallPurpose.CHOICE_HINDI,
    )


# ------------------------------------------- DND CHECKER -----------------------------------------------------------#


@app.route("/dnd", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def dnd_checker():
    if request.method == "GET":
        return flask.render_template("partials/dnd_checker.html")

    plist = request.form["plist"].split("\n")
    phones = exotel_helper.check_dnd(plist)
    return flask.render_template("partials/phoneinfo.html", phones=phones)


@app.route("/whitelist", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def whitelist():
    if request.method == "POST":
        phoneinfo = ast.literal_eval(list(request.form.keys())[0])
        phones = phoneinfo["phones"]
        exotel_helper.whitelist_numbers(phones)
    return redirect("/dnd")


@app.route("/export_dnd", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def export_dnd():
    if request.method == "POST":
        phoneinfo_obj = ast.literal_eval(list(request.form.keys())[0])
        phone_info = phoneinfo_obj["phoneinfo"]
        csv = ""
        csv += "phone,operator,status\n"
        for p in phone_info:
            csv += p["phone"] + "," + p["operator"] + "," + p["status"] + "\n"
        response = make_response(csv)
        response.headers[
            "Content-Disposition"
        ] = "attachment; filename=dndinfo_list.csv"
        return response
    return redirect("/dnd")


################ Experiment #########################


@app.route("/experiment/", methods=["GET"])
@admin_permission.require(http_exception=403)
def experiment_panel():
    return redirect("/")
    ### Will uncomment once we create the new UI for experiment
    # experiments = db_model.Experiment.query.get_experiment_log()
    # return render_template("partials/experiment_panel.html", experiments=experiments)


@app.route("/experiment/add", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_experiment():
    if request.method == "GET":
        return render_template(
            "partials/experiment_form.html",
        )

    name = request.form["name"]
    description = request.form["description"]
    start_date = datetime.strptime(
        replace_chars(request.form["start_date"]), "%m/%d/%Y"
    ).date()
    end_date = datetime.strptime(
        replace_chars(request.form["end_date"]), "%m/%d/%Y"
    ).date()

    if len(name) <= 100 and len(description) <= 1000:
        experiment = db_model.Experiment(
            name=name,
            description=description,
            start_date=start_date,
            end_date=end_date,
        )
        db.session.add(experiment)
        db.session.commit()
        return redirect(url_for("add_cohort", experiment_id=experiment.id))

    flash(
        "Name should be less than 100 characters and description should be less than 1000 characters"
    )
    return render_template(
        "partials/experiment_form.html",
    )


@app.route("/experiment/<experiment_id>/cohorts/add", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def add_cohort(experiment_id):
    if request.method == "GET":
        experiment = db_model.Experiment.query.get_by_id(experiment_id)
        return render_template("partials/cohort_form.html", experiment=experiment)

    experiment_id = request.form["experiment_id"]
    name = request.form["cohort_name"].split("\n")
    start_date = request.form["start_date"].split("\n")
    end_date = request.form["end_date"].split("\n")
    inputs = request.form["inputs"].split("\n")
    outputs = request.form["outputs"].split("\n")
    data = list(zip(name, start_date, end_date, inputs, outputs))
    db_helper.create_cohorts(data, experiment_id)
    return redirect(url_for("cohort_panel", experiment_id=experiment_id))


@app.route("/experiment/<experiment_id>/cohorts/", methods=["GET"])
@admin_permission.require(http_exception=403)
def cohort_panel(experiment_id):
    experiment = db_model.Experiment.query.get_by_id(experiment_id)
    cohorts = experiment.cohorts
    return render_template(
        "partials/cohort_panel.html", experiment=experiment, cohorts=cohorts
    )


@app.route("/experiment/<experiment_id>/cohorts/<cohort_id>", methods=["GET"])
@admin_permission.require(http_exception=403)
def cohort_details_panel(experiment_id, cohort_id):
    experiment = db_model.Experiment.query.get_by_id(experiment_id)
    cohort = db_model.Cohort.query.get_by_id(cohort_id)
    cohort_details = db_model.CohortDetails.query.get_by_cohort_id(cohort_id)
    return render_template(
        "partials/cohort_details_panel.html",
        experiment=experiment,
        cohort=cohort,
        cohort_details=cohort_details,
    )


@app.route(
    "/experiment/<experiment_id>/cohorts/<cohort_id>/<user_id>/delete",
    methods=["GET", "POST"],
)
@admin_permission.require(http_exception=403)
def delete_cohort_details(experiment_id, cohort_id, user_id):
    cohort_detail = db_model.CohortDetails.query.get_by_user_id(user_id)
    db.session.delete(cohort_detail)
    db.session.commit()
    return redirect(
        url_for(
            "cohort_details_panel", experiment_id=experiment_id, cohort_id=cohort_id
        )
    )


@app.route(
    "/experiment/<experiment_id>/cohorts/<cohort_id>/add", methods=["GET", "POST"]
)
@admin_permission.require(http_exception=403)
def add_cohort_details(experiment_id, cohort_id):
    if request.method == "GET":
        experiment = db_model.Experiment.query.get_by_id(experiment_id)
        cohort = db_model.Cohort.query.get_by_id(cohort_id)
        return render_template(
            "partials/cohort_details_form.html", experiment=experiment, cohort=cohort
        )

    users = request.form["users"].split("\n")
    users = [replace_chars(user) for user in users]
    experiences = db_model.Experience.query.find_experience_with_phone(users)
    user_ids = [experience.user_id for experience in experiences]
    users = db_model.User.query.filter(db_model.User.id.in_(user_ids)).all()
    for user in users:
        cohort_detail = db_model.CohortDetails(user_id=user.id, cohort_id=cohort_id)
        db.session.add(cohort_detail)
        db.session.commit()
    return redirect(
        url_for(
            "cohort_details_panel", experiment_id=experiment_id, cohort_id=cohort_id
        )
    )


###### dashboard ###########


@app.route("/public_dashboard", methods=["GET"])
def pubic_dashboard():
    # add check to see if our website
    CHARTIO_SECRET = app.config["CHARTIO_SECRET"]
    DASHBOARD_URL = app.config["DASHBOARD_URL"]

    payload = {
        "dashboard": int(app.config["DASHBOARD_NUMBER"]),
        "organization": int(app.config["ORGANISATION_NUMBER"]),
        "exp": datetime.now() + timedelta(seconds=30),
    }
    token = jwt.encode(payload, CHARTIO_SECRET)
    return render_template(
        "public_dashboard.html",
        dashboard_url=DASHBOARD_URL,
        chartio_token=token.decode("utf-8"),
    )


@app.route("/public_dashboard_mobile", methods=["GET"])
def pubic_dashboard_mobile():
    # add check to see if our website
    CHARTIO_SECRET = app.config["CHARTIO_SECRET"]
    DASHBOARD_URL = app.config["DASHBOARD_URL_MOBILE"]

    payload = {
        "dashboard": int(app.config["DASHBOARD_MOBILE_NUMBER"]),
        "organization": int(app.config["ORGANISATION_NUMBER"]),
        "exp": datetime.now() + timedelta(hours=12),
    }
    token = jwt.encode(payload, CHARTIO_SECRET)
    return render_template(
        "public_dashboard.html",
        dashboard_url=DASHBOARD_URL,
        chartio_token=token.decode("utf-8"),
    )


@app.route("/kookoo_callback", methods=["GET", "POST"])
def get_kookoo_callback():
    if request.method == "GET":
        # app.callbacklogutil.info(request.args)
        return json.dumps({"success": True}), 200, {"ContentType": "application/json"}

    # app.callbacklogutil.info(request.form)
    return json.dumps({"success": True}), 200, {"ContentType": "application/json"}


################################################ MISC ###############################################################


@app.errorhandler(404)
def page_not_found(e):
    return (
        flask.render_template(
            "404.html",
            prefix="Oops!! We could not find the page you requested",
            redirect_url="/",
            page_name="Main Page",
        ),
        404,
    )


@app.errorhandler(403)
def permission_denied(e):
    return (
        flask.render_template(
            "404.html",
            prefix="Sorry! You do not have permissions to visit the page",
            redirect_url="/login",
            page_name="Login Page",
        ),
        403,
    )


@app.errorhandler(405)
def method_not_allowed(e):
    return (
        flask.render_template(
            "405.html",
            message="Oops! Method not allowed",
            redirect_url="/",
            redirect_button_label="Main Page",
        ),
        405,
    )


################################################ Activity Tracking ################################################


@app.route("/activity_tracker_form", methods=["GET", "POST"])
@admin_activity_reporter_permission.union(program_associate_permission).require(
    http_exception=403
)
def activity_tracker_form():
    if request.method == "POST":
        return handle_activity_tracker_post_request()
    return handle_activity_tracker_get_request()


def prepare_activity_data(form_data):
    current_user = db_model.AdminUser.query.get(adminuser.get_current_adminuser_id())
    now = datetime.now()
    ACTIVITY_FIELDS = [
        "activity_level",
        "state",
        "district_name",
        "block_name",
        "sector_name",
        "centre_name",
        "panchayat_name",
        "school_name",
        "peeo_uceeo_school",
        "activity_type",
        "stakeholders_type",
        "other_stakeholders_type",
        "channel_type",
        "engagement_type",
        "other_engagement_type",
        "follow_up_through",
        "spd",
        "ceo",
        "aws",
        "aww",
        "awh",
        "balaww",
        "crc",
        "brc",
        "ssadc",
        "dpo",
        "cdpo",
        "beo",
        "teacher",
        "number_of_mt",
        "number_of_peeo_uceeo_cbeo",
        "home_onboarding",
        "centre_onboarding",
        "community_engagement_onboarded",
        "numbers_of_centers_participated",
        "workshop_onboarded_caregivers",
        "workshop_number_of_centre_participated",
        "notes",
    ]
    activity_data = {field: form_data.get(field) for field in ACTIVITY_FIELDS}
    activity_data["date_of_meeting"] = datetime.strptime(
        form_data.get("date_of_meeting"), "%Y-%m-%d"
    )
    activity_data.update(
        {
            "created_on": now,
            "updated_on": now,
            "updated_by": current_user.username,
        }
    )
    activity = db_model.Activities(**activity_data)
    return activity


def process_family_metrics_and_submit(activity, form_data):
    try:
        phones = {
            key: value
            for key, value in form_data.items()
            if key.startswith("user_phone_") and value
        }
        activity_type = form_data.get("activity_type")
        stakeholders_type = form_data.get("stakeholders_type")

        mode = get_identifier_mode(activity_type, stakeholders_type)

        for key, identifier in phones.items():
            index = key.split("_")[-1]
            role_data = {
                role: get_role_data(role, index, form_data, identifier, mode)
                for role in ["mother", "father", "child"]
            }

            user_phone = (
                identifier
                if mode == "phone"
                else form_data.get(f"user_phone_{index}", "")
            )

            existing_metric = find_existing_metric(mode, role_data, identifier)

            if existing_metric:
                update_existing_metric(existing_metric, role_data, user_phone)
                activity.family_metrics.append(existing_metric)
            else:
                new_metric = create_new_metric(role_data, user_phone)
                activity.family_metrics.append(new_metric)

        db.session.add(activity)
        db.session.commit()
        flash("Activity submitted successfully", "success")
        return redirect(url_for("activity_tracker_form", **form_data))

    except Exception as e:
        db.session.rollback()
        app_logger.exception(f"Error in POST request. {e}")
        flash(f"An error occurred while submitting the form. {e}", "danger")
        return redirect(url_for("activity_tracker_form", **request.form.to_dict()))


# ----------- Helpers -----------


def get_identifier_mode(activity, stakeholder):
    if activity == "Community Engagement":
        if stakeholder in ["Father meeting_bs", "father meeting"]:
            return "father_code"
        if stakeholder in ["Mother meeting_bs", "mother meeting"]:
            return "mother_code"
    return "phone"


def get_role_data(role, idx, data, ident, mode):
    increment = int(data.get(f"{role}_increment_{idx}", 0) or 0)
    name = data.get(f"{role}_name_{idx}", "")
    code = data.get(f"{role}_code_{idx}", "") or (
        ident if mode == f"{role}_code" else ""
    )
    return {"increment": increment, "name": name, "code": code}


def find_existing_metric(mode, role_data, identifier):
    if mode == "father_code" and role_data["father"]["code"]:
        return db_model.FamilyMetrics.query.filter_by(
            father_code=role_data["father"]["code"]
        ).first()
    if mode == "mother_code" and role_data["mother"]["code"]:
        return db_model.FamilyMetrics.query.filter_by(
            mother_code=role_data["mother"]["code"]
        ).first()
    return db_model.FamilyMetrics.query.filter_by(user_phone=identifier).first()


def update_existing_metric(metric, role_data, user_phone):
    for role in ["mother", "father", "child"]:
        data = role_data[role]
        if data["increment"] > 0 or data["name"] or data["code"]:
            setattr(
                metric,
                f"{role}_count",
                (getattr(metric, f"{role}_count") or 0) + data["increment"],
            )
            setattr(
                metric, f"{role}_name", data["name"] or getattr(metric, f"{role}_name")
            )
            setattr(
                metric, f"{role}_code", data["code"] or getattr(metric, f"{role}_code")
            )

    if user_phone and not metric.user_phone:
        metric.user_phone = user_phone

    metric.updated_on = datetime.now()


def create_new_metric(role_data, user_phone):
    create_kwargs = {
        "user_phone": user_phone,
        "created_on": datetime.now(),
        "updated_on": datetime.now(),
    }
    for role in ["mother", "father", "child"]:
        data = role_data[role]
        create_kwargs[f"{role}_name"] = data["name"]
        create_kwargs[f"{role}_code"] = data["code"]
        create_kwargs[f"{role}_count"] = (
            data["increment"] if data["increment"] > 0 else 0
        )
    return db_model.FamilyMetrics(**create_kwargs)


def handle_activity_tracker_post_request():
    try:
        form_data = request.form.to_dict()
        activity = prepare_activity_data(form_data)
        return process_family_metrics_and_submit(activity, form_data)
    except Exception as e:
        db.session.rollback()
        app_logger.exception(f"Error in POST request. {e}")
        flash(f"An error occurred while submitting the form. {e}", "danger")
        return redirect(url_for("activity_tracker_form", **request.form.to_dict()))


def handle_activity_tracker_get_request():
    try:
        state = request.args.get("state", "")
        activity_types = []
        if state:
            activity_types = (
                db.session.query(db_model.ActivitiesData.activity_type)
                .filter(db_model.ActivitiesData.state == state)
                .distinct()
                .order_by(db_model.ActivitiesData.activity_type)
                .all()
            )
            activity_types = [t[0] for t in activity_types if t[0]]
        return flask.render_template(
            "partials/activity_tracker_form.html",
            activity_types=activity_types,
            **request.args.to_dict(),
        )

    except Exception as e:
        app_logger.error(f"Error in handle_activity_tracker_get_request: {e}")
        flash("An error occurred while loading the form.", "danger")
        return redirect(url_for("activity_tracker_form"))


@app.route("/activities_form", methods=["GET", "POST"])
@admin_permission.require(http_exception=403)
def activities_form():
    if request.method == "POST":
        required_fields = ["state"]
        missing = [f for f in required_fields if not request.form.get(f)]
        if missing:
            flash(f"Missing required fields: {', '.join(missing)}", "warning")
            return redirect(url_for("activities_form"))

        try:
            form = request.form.to_dict()
            new_record = db_model.ActivitiesData(
                state=form.get("state"),
                district=form.get("district"),
                block=form.get("block"),
                sector=form.get("sector"),
                centre=form.get("centre"),
                panchayat_name=form.get("panchayat_name"),
                school_name=form.get("school_name"),
                activity_level=form.get("activity_level"),
                activity_type=form.get("activity_type"),
                stakeholders_type=form.get("stakeholders_type"),
                other_stakeholders_type=form.get("other_stakeholders_type"),
                follow_up_through=form.get("follow_up_through"),
                channel_type=form.get("channel_type"),
                engagement_type=form.get("engagement_type"),
                other_engagement_type=form.get("other_engagement_type"),
            )
            db.session.add(new_record)
            db.session.commit()
            flash("New activity mapping added successfully!", "success")
        except Exception as e:
            db.session.rollback()
            app_logger.error(f"Failed to save new activity data: {e}")
            flash(f"Error: {str(e)}", "danger")

        return redirect(url_for("activities_form"))

    return render_template("partials/activities_form.html")
