#!/usr/bin/env python3

from dotenv import load_dotenv
from pathlib import Path
from datetime import datetime
from discord_webhook import DiscordWebhook

import urllib
import json
import os
import traceback
import sys
import re
import subprocess
import requests
import io

# Load dotenv file if possible.
# TODO: change all scripts to use named flags/params
if len(sys.argv) > 1:
    env_path = Path(sys.argv[1])
    load_dotenv(dotenv_path=env_path, override=True)

# Get the container name as an argument or use "sia" as default.
CONTAINER_NAME = "sia"
if len(sys.argv) > 2:
    CONTAINER_NAME = sys.argv[2]

# sc_precision is the number of hastings per siacoin
sc_precision = 10**24

# Environment variable globals
setup_done = False


# get docker container id and return None if container not found
def get_docker_container_id(container_name):
    docker_cmd = "docker ps -q -f name=" + container_name
    output = subprocess.check_output(docker_cmd, shell=True).decode("utf-8")
    return None if output == "" else output


# find out local siad ip by inspecting its docker container
def get_docker_container_ip(container_name):
    ip_regex = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
    docker_cmd = (
        "docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "
        + container_name
    )
    output = subprocess.check_output(docker_cmd, shell=True).decode("utf-8")
    return ip_regex.findall(output)[0]


# sia deamon local ip address with port
api_endpoint = "http://{}:{}".format(
    get_docker_container_ip(CONTAINER_NAME), os.getenv("API_PORT", "9980")
)


# find siad api password by getting it out of the docker container
def get_api_password():
    if os.getenv("SIA_API_PASSWORD"):
        return os.getenv("SIA_API_PASSWORD")

    api_password_regex = re.compile(r"^\w+$")
    docker_cmd = "docker exec {} cat /sia-data/apipassword".format(CONTAINER_NAME)
    output = subprocess.check_output(docker_cmd, shell=True).decode("utf-8")
    return api_password_regex.findall(output)[0]


def setup():
    siad.initialize()

    global setup_done
    setup_done = True


# send_msg sends the msg to the specified discord channel.
# If force_notify is set to true it adds "@here".
async def send_msg(msg, force_notify=False, file=None):
    try:
        webhook_url = os.getenv("DISCORD_WEBHOOK_URL")
        webhook_mention_user_id = os.getenv("DISCORD_MENTION_USER_ID")
        webhook_mention_role_id = os.getenv("DISCORD_MENTION_ROLE_ID")
        webhook = DiscordWebhook(url=webhook_url, rate_limit_retry=True)

        # Add the portal name.
        msg = "**{}**: {}".format(os.getenv("SERVER_DOMAIN"), msg)

        if file and isinstance(file, str):
            is_json = is_json_string(file)
            content_type = "application/json" if is_json else "text/plain"
            ext = "json" if is_json else "txt"
            filename = "{}-{}.{}".format(
                CONTAINER_NAME, datetime.utcnow().strftime("%Y-%m-%d-%H:%M:%S"), ext
            )
            skylink = upload_to_skynet(file, filename, content_type=content_type)
            if skylink:
                msg = "{} {}".format(msg, skylink)  # append skylink to message
            else:
                webhook.add_file(file=io.BytesIO(file.encode()), filename=filename)

        if force_notify and (webhook_mention_user_id or webhook_mention_role_id):
            webhook.allowed_mentions = {
                "users": [webhook_mention_user_id],
                "roles": [webhook_mention_role_id],
            }
            msg = "{} /cc".format(msg)  # separate message from mentions
            if webhook_mention_role_id:
                msg = "{} <@&{}>".format(msg, webhook_mention_role_id)
            if webhook_mention_user_id:
                msg = "{} <@{}>".format(msg, webhook_mention_user_id)

        webhook.content = msg
        webhook.execute()

        print("msg > " + msg)  # print message to std output for debugging purposes
    except:
        print("Failed to send message!")
        print(traceback.format_exc())


def upload_to_skynet(contents, filename="file.txt", content_type="text/plain"):
    files = {"file": (filename, contents, content_type)}
    res = requests.post("https://siasky.net/skynet/skyfile", files=files)
    if res.status_code == requests.codes["ok"]:
        res_json = res.json()
        return "https://siasky.net/" + res_json["skylink"]
    return None


def is_json_string(str):
    try:
        json.loads(str)
        return True
    except ValueError:
        return False


# siad class provides wrappers for the necessary siad commands.
class siad:
    # initializes values for using the API (password and
    # user-agent) so that all calls to urllib.request.urlopen have these set.
    @staticmethod
    def initialize():
        # Setup a handler with the API password
        username = ""
        password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
        password_mgr.add_password(None, api_endpoint, username, get_api_password())
        handler = urllib.request.HTTPBasicAuthHandler(password_mgr)

        # Setup an opener with the correct user agent
        opener = urllib.request.build_opener(handler)
        opener.addheaders = [("User-agent", "Sia-Agent")]

        # Install the opener.
        # Now all calls to urllib.request.urlopen use our opener.
        urllib.request.install_opener(opener)

    # load_json reads the http response and decodes the JSON value
    @staticmethod
    def load_json(resp):
        return json.loads(resp.decode("utf-8"))

    @staticmethod
    def get(endpoint):
        if not setup_done:
            setup()

        resp = urllib.request.urlopen(api_endpoint + endpoint).read()
        return siad.load_json(resp)

    @staticmethod
    def get_wallet():
        if not setup_done:
            setup()

        resp = urllib.request.urlopen(api_endpoint + "/wallet").read()
        return siad.load_json(resp)

    @staticmethod
    def get_renter():
        if not setup_done:
            setup()

        resp = urllib.request.urlopen(api_endpoint + "/renter").read()
        return siad.load_json(resp)

    @staticmethod
    def get_renter_contracts():
        if not setup_done:
            setup()

        resp = urllib.request.urlopen(api_endpoint + "/renter/contracts").read()
        return siad.load_json(resp)
