from signedjson.key import read_signing_keys
from signedjson.sign import sign_json
import json

import requests
from systemd import journal
from get_config import read_vars

import datetime

# Importing configuration from environment variables
config = read_vars()


# Getting signing key
with open(config["server_key_file"]) as f:
    skey = read_signing_keys(f)[0]


# Defining additional functions
def current_time():
    now = datetime.datetime.now()
    return int(now.timestamp() * 1000)

def parse_roomid(roomid):
    return roomid.split(":")[1]

def serveraddr(servername):
    x = requests.get("https://%s/.well-known/matrix/server" % servername)
    if x.status_code == 200:
        j = json.loads(x.text)
        return j["m.server"]
    else:
        journal.send(f"[WARNING] Got {x.status_code} discovering server address of {servername}")
        return False


# Function for getting users' access tokens
def get_access_token(userid):
    time = current_time() + config["auth_token_term"] * 1000
    x = requests.post(
        f"https://{config['origin_server']}/_synapse/admin/v1/users/{userid}/login",
        json={"valid_until_ms": time},
        headers={
            "Authorization": "Bearer %s" % config["admin_auth_token"],
            "Content-Type": "application/json",
        },
    ).text
    if "You are not a server admin" in x:
        journal.send("ERROR! The given token doesn't belogn to the server admin!")
        exit()
    y = json.loads(x)
    return y["access_token"]


# Sign Matrix federation API requests
## From Matrix protocol documentation
def authorization_headers(
    destination_name, request_method, request_target, content=None
):
    origin_name = config["origin_server_name"]
    request_json = {
        "method": request_method,
        "uri": request_target,
        "origin": origin_name,
        "destination": destination_name,
    }

    if content is not None:
        request_json["content"] = content

    signed_json = sign_json(request_json, origin_name, skey)

    for key, sig in signed_json["signatures"][origin_name].items():
        return 'X-Matrix origin="%s",destination="%s",key="%s",sig="%s"' % (
            origin_name,
            destination_name,
            key,
            sig,
        )


# Request the last event
def request_last_event(destination, roomid):
    servers = [destination, "matrix.org", "inex.rocks", "sibnsk.net", "kde.org"]
    for i in servers:
        journal.send(f"[info] Trying access event_to_timestamp via {i}")
        try:
            server = serveraddr(i)
        except requests.exceptions.ConnectionError:
            journal.send(f"[WARNING] {i}'s well-known's are unreachable")
            continue
        except:
            journal.send(f"[WARNING] Can't request {i}'s well-known's for unknown reason. Likely {i} is unreachable or ssl certs expired")
            continue
        if server == False:
            continue

        res = current_time()
        auth = authorization_headers(
            i,
            "GET",
            f"/_matrix/federation/v1/timestamp_to_event/{roomid}?dir=b&ts={res}",
        )
        try:
            x = requests.get(
                f"https://{server}/_matrix/federation/v1/timestamp_to_event/{roomid}",
                params={"dir": "b", "ts": res},
                headers={"Authorization": auth},
            ).text
        except requests.exceptions.ConnectionError:
            journal.send(f"[WARNING] {server} is unreachable")
            continue
        except:
            journal.send(f"[WARNING] Couldn't request timestamp_to_event from {server} for unknown reason. Likely {server} is unreachable or ssl certs expired")
            continue

        if "M_UNRECOGNIZED" in x:
            journal.send(f"[WARNING] Trying request {server} timestamp_to_event for {roomid}: got M_UNRECOGNIZED error")
            continue
        elif "M_NOT_FOUND" in x:
            journal.send(f"[WARNING] Unable to get the last event from {roomid}. {i} doesn't know room {roomid}")
            journal.send(f"{i} says: '{x}'")
            continue
        elif "event_id" in x:
            j = json.loads(x)
            return (i, server, j["event_id"])
        else:
            journal.send(f"[WARNING] Unable to get the last event from {roomid}. Unknown error from {server}: '{x}'")
            continue

    journal.send(f"ERROR! Unable to get the last event from {roomid}. All {servers} don't know room {roomid} or support timestamp_to_event")
    return False


# Request states of specified room
def get_states(destination, server, roomid, last_event_id):
    auth = authorization_headers(
        destination, "GET", "/_matrix/federation/v1/state/%s?event_id=%s" % (roomid, last_event_id)
    )
    return requests.get(
        f"https://{server}/_matrix/federation/v1/state/{roomid}?event_id={last_event_id}",
        headers={"Authorization": auth},
    ).text


# Get users list from states
def get_users(states):
    x = json.loads(states)
    # jq .pdus.[].type
    users = []
    x = x["pdus"]
    for i in x:
        if i["type"] == "m.room.member":
            users.append(i["sender"])
    return users

def filter_users(users):
    x = []
    for i in users:
        if f":{config["origin_server_name"]}" in i:
            x.append(i)
    return x


# Make a user join a room
def mkjoins(roomid, users, server1, server2):
    for i in users:
        token = get_access_token(i)
        x = requests.post(
            f"https://{config['origin_server']}/_matrix/client/v3/join/{roomid}",
            params={"via": server1, "via": server2},
            headers={"Authorization": "Bearer %s" % token},
        ).text
        if roomid in x:
            journal.send(f"[info] Joined {i} to {roomid}")
            journal.send(x)
            return True
        else:
            journal.send(f"[info] Failed to join {i} to {roomid}")
            journal.send(x)
            return False


def revitalize(roomid, server):
    journal.send(f"Got new roomid {roomid}")
    fe = request_last_event(server, roomid)
    if fe == False:
        return False
    else:
        x = get_states(fe[0], fe[1], roomid, fe[2])
        y = get_users(x)
        z = filter_users(y)
        journal.send(f"Trying to add {z} to {roomid}")
        if mkjoins(roomid, z, server, fe[0]):
            return True
        else:
            return False
