#!/usr/bin/env python3

import subprocess
import os
import json
import sys
from argparse import ArgumentParser
from getpass import getpass


def msgshow(msg, exit=False):
    if not ARG.quiet:
        # if silent mode is set, script doesn't output
        print(msg)
    if exit:
        # if exit option is set, script exits after showing message
        exit()


def getIP():
    host_url = "https://regdelivery.de/ip-api.php"  # default host to fetch IP
    ip = []
    if ARG.v4:
        if "." not in str(ARG.v4):
            curl_opts_4 = [
                "curl",
                "-4",
                "-s",
                host_url,
            ]  # curl sending IPv4 req to API server.
            try:
                msgshow("Fetching IPV4 from cloud API server.")
                curl_output_4 = str(subprocess.check_output(curl_opts_4), "utf-8")
            except subprocess.CalledProcessError:
                # throwing error if curl can not fetch ip
                print(
                    """
                    cURL threw an exception. It could mean either it is not
                    installed or it could not establish a connection to IP
                    API server.
                    """
                )
                print("Please make sure you have an active internet connection.")
                print("Error: API_FETCH_ERROR_4")
                exit()
            msgshow("Got IPv4 successfully. [" + curl_output_4 + "]")
            ip.append(curl_output_4)  # appending ipv4 to output array
        else:
            msgshow(f"Taking v4 [{ARG.v4}]")
            ip.append(ARG.v4)
    if ARG.v6:
        if ":" not in str(ARG.v6):
            curl_opts_6 = [
                "curl",
                "-6",
                "-s",
                host_url,
            ]
            # curl sending IPv4 req to API server.
            try:
                msgshow("Fetching IPV6 from cloud API server.")
                curl_output_6 = str(subprocess.check_output(curl_opts_6), "utf-8")
            except subprocess.CalledProcessError:
                # throwing error if curl can not fetch ip
                print(
                    """
                    cURL threw an exception. It could mean either it is not
                    installed or it could not establish a connection to IP
                    API server.
                    """
                )
                print("Please make sure you have an active internet connection.")
                print("Error: API_FETCH_ERROR_6")
                exit()
            msgshow("Got IPV6 successfully. [" + curl_output_6 + "]")
            ip.append(curl_output_6)  # appending ipv6 to output array
        else:
            msgshow(f"Taking v6 [{ARG.v6}]")
            ip.append(ARG.v6)
    return ip


def readConfig(url):
    global CONFIG_URL
    try:  # reading config file
        with open(url, "r") as f:
            pars_json = json.load(f)
    except OSError:  # throws error if config file could not be read
        print("Could not read config file @ " + CONFIG_URL)
        print("Error: FILE_READ_ERROR")
        exit()
    except json.JSONDecodeError:  # throws error if file was not in JSON format
        print("Error parsing config file @ " + CONFIG_URL)
        print("Check if file format is conform to this script's requirement.")
        print("Error: JSON_DECODE_ERROR")
        exit()
    req_keys = ["username", "password", "domain"]
    if list(pars_json.keys()) != req_keys:
        print("Error parsing parameters from config file @ " + CONFIG_URL)
        print("Check if file syntax is conform to this script's requirement.")
        print("Error: PARAMETER_PARSE_ERROR")
        exit()
    return pars_json


def analyzeCurlOutput(output):
    # contains list of possible server responses
    #  source: https://help.dyn.com/remote-access-api/return-codes/
    server_response_list = {
        "badauth": "Server stated authentification data was not correct, please check and try again later.",
        "good": "IP update completed successfully, new ip was successfully written to DNS records.",
        "nochg": "IP update completed successfully, yet no changes were made.",
        "notfqdn": "The hostname specified is not a fully-qualified domain name (not in the form hostname.dyndns.org or domain.com).",
        "nohost": "The hostname specified does not exist in this user account (or is not in the service specified in the system parameter).",
        "numhost": "Too many hosts (more than 20) specified in an update. Also returned if trying to update a round robin (which is not allowed).",
        "abuse": "The hostname specified is blocked for update abuse.",
        "badagent": "The user agent was not sent or HTTP method is not permitted (we recommend use of GET request method).",
        "dnserr": "DNS error encountered.",
        "911": "There is a problem or scheduled maintainance on Strato's Servers.",
    }
    try:
        output = output.split(" ")[0]  # Splitting output from update request.
        msgshow(server_response_list[output])
    except:
        print("Error occured while trying to parse output from the update server.")
        print("The request has already been sent.")
        print("Server sent : ")
        print(output)
    return True


def createDnsUpdateRequest(
    username, password, hostname, ipv4="", ipv6=""
):  # updates the dns record finally
    updateURL = ""
    if ipv4 and ipv6:
        updateURL = (
            "https://"
            + username
            + ":"
            + password
            + "@dyndns.strato.com/nic/update?hostname="
            + hostname
            + "&myip="
            + ipv4
            + ","
            + ipv6
        )
        showURL = (
            "https://"
            + username
            + ":***********@dyndns.strato.com/nic/update?hostname="
            + hostname
            + "&myip="
            + ipv4
            + ","
            + ipv6
        )
    if not ipv4:
        updateURL = (
            "https://"
            + username
            + ":"
            + password
            + "@dyndns.strato.com/nic/update?hostname="
            + hostname
            + "&myip="
            + ipv6
        )
        showURL = (
            "https://"
            + username
            + ":***********@dyndns.strato.com/nic/update?hostname="
            + hostname
            + "&myip="
            + ipv6
        )
    if not ipv6:
        updateURL = (
            "https://"
            + username
            + ":"
            + password
            + "@dyndns.strato.com/nic/update?hostname="
            + hostname
            + "&myip="
            + ipv4
        )
        showURL = (
            "https://"
            + username
            + ":***********@dyndns.strato.com/nic/update?hostname="
            + hostname
            + "&myip="
            + ipv4
        )
    try:
        if ARG.secure:
            msgshow("Hitting: " + showURL)
        if not ARG.secure:
            msgshow("Hitting: " + updateURL)
        curl_dns_array = ["curl", "-s", updateURL]
        curl_dns_output = str(
            subprocess.check_output(curl_dns_array), "utf-8"
        )  # sending curl request to Strato Servers
    except subprocess.CalledProcessError:  # throwing error if curl can not fetch ip
        print(
            "Script encountered a problem. It could mean either cURL is not installed or it could not establish a connection to Strato DynDNS server."
        )
        print("Please make sure you have an active internet connection.")
        print("Error: CURL_DNS_REQ_NOT_POSSIBLE_1")
        exit()
    except:
        print("Script encountered a problem while sending update request to server.")
        print("Error: CURL_DNS_REQ_NOT_POSSIBLE_2")
    analyzeCurlOutput(curl_dns_output)
    return True


def generateConf():
    data = {}
    data["username"] = []
    data["password"] = []
    data["domain"] = []
    username = input(
        "Enter username for your domain, if you are trying to update subdomain records, username is mostly main domain. [FQDN]: "
    )
    password = getpass("Password for record update: ")
    domain = input("Domain or subdomain to be updated.[FQDN]: ")
    name = input("Configuration file name [Must end with .conf, e.g. config.conf]: ")
    path = input("Where do you want the file to be stored?(. for current folder): ")

    # processing input to create json file
    data["username"] = username
    data["password"] = password
    data["domain"] = domain

    save_url = os.path.join(path, name)  # creating final file name
    try:
        with open(save_url, "w") as conf_file:
            json.dump(data, conf_file)  # writing json data
        msgshow("Created a conf file successfully.")
        decs = input("Input n to --> EXIT <--- if you just wanted to create a config file. ")
        if decs == "n":
            exit()
        decs = input("Do you want to use this config file now? [y/n] ")
        if decs == "y":
            global CONFIG_URL
            CONFIG_URL = save_url
    except OSError:
        msgshow("Could not write to config file, no file was generated.")


def main(argv):

    # Adding command line arguments
    parser = ArgumentParser()
    parser.add_argument(
        "--input",
        "-i",
        help="Input a config file, configuration format found in https://github.com/regmibijay/strato-dyndns",
    )
    parser.add_argument("-v4", help="Update IPV4", const=True, nargs="?")
    parser.add_argument("-v6", help="Update IPV6", const=True, nargs="?")
    parser.add_argument(
        "--config",
        "-c",
        help="Create a configuration file containing your authentification data",
        const=True,
        nargs="?",
    )
    parser.add_argument("--quiet", "-q", help="Program operates silently", const=True, nargs="?")
    parser.add_argument(
        "--version",
        "-v",
        help="Shows the version number of this tool.",
        const=True,
        nargs="?",
    )
    parser.add_argument(
        "--secure",
        "-s",
        help="Hides the password in logs and console texts.",
        const=True,
        nargs="?",
    )
    arg = parser.parse_args()

    # making args global so all functions can acess them
    global ARG
    global CONFIG_URL
    ARG = arg
    CONFIG_URL = "config.conf"
    # showing greeting
    msgshow("Strato DynDNS Client")
    msgshow("Made with love by Bijay Regmi. ")
    msgshow("Contributions and feedback welcome @ https://github.com/regmibijay/strato-dyndns")
    # checking what arguments user specified:
    if arg.version:
        print(
            """
        For script version, do \n
        pip show strato-dyndns
        """
        )
        exit()
    if arg.input:
        msgshow("Using " + arg.input + " as input file.")
        CONFIG_URL = arg.input
    if arg.v4:
        msgshow("IPV4 will be determined and updated")
    if arg.v6:
        msgshow("IPV6 will be determined and updated")
    if arg.config:
        msgshow("Configuration wizard will be ran to generate a config file.")
    if arg.secure:
        msgshow("Password will be hidden from logs.")
    msgshow("_________________________")
    # checking if user wants to create a new config file
    if arg.config:
        msgshow("Press Ctrl + c to exit config generation wizard.")
        try:
            generateConf()
        except KeyboardInterrupt:
            msgshow(
                "\r\nOk, then no config file will be generated. Press Ctrl+c again  if you meant to exit this script."
            )
    # cross checking these arguments if contradict each other
    if not arg.v4 and not arg.v6:  # if none of ip are set, script exits
        print("Please specify at least one IP address type with either -v4 or -v6 to update.")
        exit()
    if arg.input and not os.path.exists(arg.input):
        print("Specified config file does not exit, please verify and try again")
        exit()
    msgshow("_________________________")

    ip = getIP()  # call to IP fetch function
    msgshow("_________________________")

    # reading parameters from config file
    msgshow("Reading config file " + CONFIG_URL)
    parameters = readConfig(CONFIG_URL)
    msgshow("Domain Name:" + parameters["domain"])
    msgshow("Username: " + parameters["username"])
    if not arg.secure:  # removing password if secure flag is set
        msgshow("Password: " + parameters["password"])
    if arg.secure:
        msgshow("Password: " + "********")
    # creating Update request now
    ipv4 = ""
    ipv6 = ""
    if arg.v6 and arg.v4:
        ipv4 = ip[0]
        ipv6 = ip[1]
    if not arg.v6:
        ipv4 = ip[0]
    if not arg.v4:
        ipv6 = ip[0]
    msgshow("_________________________")
    try:
        createDnsUpdateRequest(
            username=parameters["username"],
            password=parameters["password"],
            hostname=parameters["domain"],
            ipv4=ipv4,
            ipv6=ipv6,
        )
        msgshow("_________________________")
    except Exception:
        print("could not create request")
        msgshow("_________________________")


if __name__ == "__main__":
    # if this file is ran from bash, main function is called.
    main(sys.argv[1:])
