import requests
import urllib
import bs4 # BeautifulSoup
import json
import subprocess
import os
import sys

# For possible compatibility with Python2. It still may break under
# Python2 somewhat though. It is highly recommended that just use
# Python3 to run the script
try:
    import urllib.parse as urlparse
except:
    import urlparse

# User agents sent by the requests.
# This is how server decide what information it want to send via the docshow_api
# In case your app failed to launch, you might want to check with the server
# to see if there is change made to the format of urlprotocol in the latest version.
# Especially if you could launch older version OS successfully.
user_agent = {
    "windows": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv 11.0) like Gecko",
    "mac": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) " \
           "AppleWebKit/537.36 (KHTML, like Gecko) " \
           "Chrome/75.0.3770.100 Safari/537.36"
}

# Configuration Settings
# TODO Use other mechanisms to get configuration settings
first_name = "cc13"
last_name = "cc14"
email = "admin@qa.webex.com"
site_url = "train.qa.webex.com"
languageId = "1"
userName = "admin"
password = "P@ss1234"
service_type = "MC"
meetingTopic = "I am a test"
meeting_password = "123456"
tele_conf = 1
# tele_desc = "&TD="
# tele_tspara = "&TA="


# Glogal configurations. These points to specific website api
# They should not be changed unless the website api changed
meeting_API = "m.php"
login_API = "p.php"
docshow_API = "wbxmjs/api/v1/meetings/simpleflowmapping"
# It is used in scheduling meetings at m.php
# Not sure how this works. Ask Server side programmer before changing it.
trackingCodesParam = "&TC1=1&TC2=2&TC3=3&TC4=4&TC5=5&TC6=6&TC7=7&TC8=8&TC9=9&TC10=10"

# Global variables created at runtime.
#
# session:
# Session is used to preserve the cookies and headers in the whole session.
# Currently session could only be created once to ensure the completeness of
# the session status. If you want to schedule and start multiple metting you
# can reuse the same session and you don't need to login again. If you keep
# running the same python interpreter as a server you might want to pass
# session as a parameter among function to avoid all kind of bizarre problems.
#
# meeting_number:
# meeting number returned in the schedule meeting phase.
# This is the most important info. This is required to start a meeting.
# See fetch_meeting_number and schedule_meeting for how it is fetched.
session = None
meeting_number = "236549245"

# Params under the name AT in the api call.
# For m.php
schedule_meeting_action = "SM"
start_meeting_action = "HM"
join_action = "JM"
# For p.php
login_action = "LI"


# In case of future use: This is for switching service to support EC and TC
# switchSericeUrl = siteUrl + 'o.php?AT=ST&SP=' + serviceType
# Example : 'https://pl.qa.webex.com/fr26java2/o.php?AT=ST&SP=' + 'MC'


def construct_join_meeting_url():
    param_dict = dict()
    param_dict['AT'] = join_action
    param_dict['MK'] = meeting_number
    param_dict['PW'] = meeting_password
    param_dict['FN']=first_name
    param_dict['LN']=last_name
#    param_dict['AN'] = "{first} {last}".format(first=first_name, last=last_name)
    param_dict['AE'] = email
    params = urlparse.urlencode(param_dict)
    #params += trackingCodesParam
    url = "https://{site_url}/{meeting_API}?{params}".format(
        site_url=site_url, meeting_API=meeting_API, params=params)
    return url
print(construct_join_meeting_url())


def construct_schedule_meeting_url():
    """
    This function returns the whole parameter for scheduling meetings.
    It constructs a dictionary to store all the variables needed and concatenate
    these variables via the urlencode function
    :return:
    """
    param_dict = dict()
    param_dict['AT'] = schedule_meeting_action
    param_dict['MN'] = meetingTopic
    param_dict['PW'] = meeting_password
    param_dict['TC'] = str(tele_conf)
    param_dict['LF'] = "1"
    param_dict['NT'] = "1"
    params = urlparse.urlencode(param_dict)
    params += trackingCodesParam
    url = "https://{site_url}/{site_name}/{meeting_API}?{params}".format(
        site_url=site_url, site_name=site_url.split(".")[0],
        meeting_API=meeting_API, params=params)
    return url


def construct_login_url():
    """
    This function returns the whole parameter for login.
    It constructs a dictionary to store all the variables needed and concatenate
    these variables via the urlencode function
    :return:
    """
    param_dict = dict()
    param_dict['AT'] = login_action  # Action is login
    param_dict['WID'] = userName
    param_dict['PW'] = password
    param_dict['Language'] = languageId
    params = urllib.parse.urlencode(param_dict)
    url = "https://{site_url}/{site_name}/{login_API}?{params}".format(
        site_url=site_url, site_name=site_url.split(".")[0],
        login_API=login_API, params=params)
    return url
print(construct_login_url())

def construct_post_json(uuid, joinToken):
    """
    This returns a JSON format string to POST on docshow API.
    This is the only JSON API we are currently using.
    :param uuid: This can be fetched from m.php
    :param joinToken: Same as uuid
    :return: The constructed json format string
    """
    post_dict = dict()
    post_dict['displayname'] = "{first} {last}".format(first=first_name, last=last_name)
    post_dict['downloadOnly'] = False
    post_dict['email'] = email
    post_dict['enableInAppJoin'] = True
    post_dict['joinToken'] = joinToken
    post_dict['locale'] = "en_US"
    post_dict['meetingkey'] = meeting_number
    post_dict['meetinguuid'] = uuid
    post_dict['mtid'] = ""
    post_dict['serviceType'] = service_type
    post_dict['siteUrl'] = site_url.split(".")[0]
    post_dict['thinSwitch'] = dict()
    return json.dumps(post_dict)


def fetch_meeting_number(schedule_page):
    """
    The program fetches and returns the meeting number from what is returned from
    m.php in the function schedule_meeting. This function uses BeautifulSoup
    Library to simplify the operation and makes it more readable.
    :param schedule_page: The page returned by schedule meeting
    :return: The meeting number we scheduled
    """
    soup = bs4.BeautifulSoup(schedule_page, features="html.parser")
    forms = soup.find_all("form")
    assert len(forms) == 1
    submit_form = forms[0]
    url = submit_form.attrs['action']
    parsed = urlparse.urlparse(url)
    get_params = urlparse.parse_qs(parsed.query)
    return get_params['MK'][0]


def start_session(platform=None):
    """
    This is the function to start a requests session. Only one session could
    be initialized. Call it twice from the same interpreter will return in an
    exception. Delete the assertion if you don't want that restriction.
    Just be aware everything including headers and cookies are stored in the
    session so think twice when you want to initialize it again.
    :param platform: Specifically specify the platform for
    :return:
    """
    global session
    assert session is None, \
        AssertionError("There is a session started already!")
    session = requests.session()

    # determine what user-agent should be used based on the platform
    if platform is not None:
        session.headers.update({"User-Agent": user_agent[platform]})
    else:
        if sys.platform == "darwin":
            session.headers.update({"User-Agent": user_agent['mac']})
        else:
            session.headers.update({"User-Agent": user_agent['windows']})

    # Add fake csrt token. Required component for the last step
    session.headers.update({"X-CSRF-TOKEN": "asdf", })
    # For convenience and makes error easier to trace
    session.cookies.set("protocolInstallSuccess", "success")


def login():
    res = session.get(construct_login_url())
    assert "ticket" in res.cookies, \
        AssertionError("Login failed with response:{text}".format(text=res.text))
    return True


def schedule_meeting():
    global meeting_number
    res = session.get(construct_schedule_meeting_url())
    meeting_number = fetch_meeting_number(res.text)


def construct_start_meeting_url():
    param_dict = dict()
    param_dict['AT'] = start_meeting_action
    param_dict['MK'] = meeting_number
    params = urllib.parse.urlencode(param_dict)
    url = "https://{site_url}/{site_name}/{meeting_API}?{params}".format(
        site_url=site_url, site_name=site_url.split(".")[0],
        meeting_API=meeting_API, params=params)
    return url
print(construct_start_meeting_url())

def start_meeting():
    res = session.get(construct_start_meeting_url())

    # Fetch the url the site want us to jump from the result
    # If you don't know why this work, do a debug run
    text = res.text
    start = text.find("top.location ='")
    quote_start = text.find("'", start)
    quote_end = text.find("'", quote_start + 1)
    jump_url = text[quote_start + 1:quote_end]

    # Parse the escape characters in the string
    jump_url = jump_url.encode().decode("unicode_escape")

    # Fetch the UUID and joinToken from the url. The url format should be:
    # {site_path}/{UUID}?joinToken={joinToken}
    parsed = urlparse.urlparse(jump_url)
    get_params = urlparse.parse_qs(parsed.query)
    joinToken = get_params['joinToken'][0]
    uuid = parsed.path.split("/")[-1]
    post_data = construct_post_json(uuid, joinToken)
    url = urlparse.urljoin("https://{site_url}".format(site_url=site_url), docshow_API)
    url += "?siteurl={site_name}".format(site_name=site_url.split(".")[0])
    res = session.post(url, data=post_data, headers={"Content-Type": "application/json"})
    json_result = res.text
    json_result = json.loads(json_result)
    launch_app(json_result)


def launch_app(json_return):
    """
    Launch the actual application from the urlProtocollink.
    Obviously how works is platform dependent.
    :param json_return: The json file returned from the docshow API
    :return: Nothing
    """
    if "darwin" in sys.platform:
        subprocess.Popen(["open", json_return['urlProtocollink']])
    elif "win" in sys.platform:
        path = os.path.expanduser("~/AppData/Local/Webex/webex.exe")
        subprocess.Popen([path, json_return['urlProtocollink']])


start_session()
login()
schedule_meeting()
start_meeting()

res = session.get(construct_join_meeting_url())
print(res)