#!/usr/bin/python3.9
# ruff: noqa: T201

"""Import exported tar.gz config to group"""

import argparse
import base64
import hashlib
import hmac
import json
import sys
import tarfile
import time
from functools import partial
from pathlib import Path
from typing import Any, Optional
from urllib.error import HTTPError
from urllib.request import Request, urlopen

parser = argparse.ArgumentParser(description="Export part of UCS configuration")
parser.add_argument("tar", help="export.tar.gz")
parser.add_argument("group_id", help="branch ID to export")
parser.add_argument("-a", dest="url", action="append", help="UCS API URL", default="http://127.0.0.1:1234/JSON")
parser.add_argument("-k", dest="key", action="append", help="uAuth PSK", default="file:///etc/uauth/psk")
parser.add_argument("-u", dest="user", action="append", help="username to execute request as", default="admin")
args = parser.parse_args()

def jwt(key: str = "file:///etc/uauth/psk", user: str = "admin") -> str:
    """Make JWT for uAuth"""
    if key.startswith("file://"):
        key = Path(key.removeprefix("file://")).read_text().rstrip()

    segments = []
    header = {"typ": "JWT", "alg": "HS256"}
    json_header = json.dumps(header, separators=(",", ":"), sort_keys=True).encode("utf8")
    segments.append(base64.urlsafe_b64encode(json_header).replace(b"=", b""))
    payload = {"sub": user, "iat": int(time.time())}
    json_payload = json.dumps(payload, separators=(",", ":")).encode("utf8")
    segments.append(base64.urlsafe_b64encode(json_payload).replace(b"=", b""))
    signing_input = b".".join(segments)
    signature = hmac.new(key.encode("utf-8"), signing_input, hashlib.sha256).digest()
    segments.append(base64.urlsafe_b64encode(signature).replace(b"=", b""))
    return b".".join(segments).decode("utf-8")


class ApiError(Exception):
    """API reported error"""

    def __init__(self, message: str, code: int, data: Optional[dict[str, str]] = None):
        super().__init__(message)
        self.message = message
        self.code = code
        self.data = data


def ucs_rpc(url: str, user: str, key: str, method: str, *args: list[Any]) -> Any:  # noqa: ANN401 - we really don't know the type
    """Do UCS-JSON-RPC request"""
    url = f'{url.removesuffix("/")}/{method}'
    data = json.dumps([args, {}, {}]).encode("utf-8")
    headers = {
        "Authorization": f"uAuth {jwt(key, user)}",
        "Content-Type": "application/json",
    }
    if not url.startswith(("http://", "https://")):
        message = "API URL must be HTTP or HTTPS"
        raise OSError(message)

    req = Request(url, data, headers)  # noqa: S310 - wtf, we check it above
    try:
        with urlopen(req) as response:  # noqa: S310 - wtf, we check it above
            return json.load(response.fp)

    except HTTPError as error:
        try:
            ucs_error = json.load(error.fp)
            message = [f'UCS FAIL: {ucs_error["message"]}']
            data = ucs_error.get("data")
            if ucs_error["code"] == 0:  # Input validator error
                for k, v in data.items():
                    message.append(f" * {k}: {v}")
            code = min(abs(ucs_error["code"]), 90)
            raise ApiError("\n".join(message), code=code, data=data)

        except (ValueError, KeyError):
            message = f"API FAIL: {error}"
            raise ApiError(message, code=98) from error

    except OSError as error:
        message = f"HTTP FAIL: {error}"
        raise ApiError(message, code=99) from error


ucs = partial(ucs_rpc, args.url, args.user, args.key)

try:
    group = ucs("config.groups.get", args.group_id)
except ApiError as error:
    print(f"Unable to get parent group ID {args.group_id}: {error}")
    sys.exit(1)

group_map = {}

with tarfile.open(args.tar, "r:gz") as tar:
    config = json.load(tar.extractfile("config.json"))

    groups = config["groups"]
    extensions = config["extensions"]
    settings = config["settings"]
    timeperiods = config["timeperiods"]
    soundfiles = config["soundfiles"]

    try:
        children = ucs("structure.childs", args.group_id)
    except ApiError as error:
        print(f"Unable to get parent group ID {args.group_id}: {error}")
        sys.exit(1)

    root = groups[0]

    if any(child["name"] == root["name"] for child in children):
        print(f"""Root group of export "{root["name"]}" already exists in parent group "{group["name"]}"!""")
        sys.exit(1)

    group_map[root["parent"]] = args.group_id
    for group in groups:
        exported_group_id = group.pop("id")
        group["parent"] = group_map.get(group["parent"])
        try:
            result = ucs("config.groups.add", group)
        except ApiError as error:
            print(f"""Unable to create group {group["name"]}: {error}""")
            continue

        print(f"""Created group {group["name"]} with ID {result}""")
        group_map[exported_group_id] = result
        time.sleep(.5)  # to let UCS reload Tree


    for soundfile in soundfiles:
        if soundfile["directory"]:
            continue  # TODO: create directories by export

        path = soundfile.pop("path")
        try:
            current = ucs("config.soundfiles.get", f"{path}")
            print(f"Soundfile directory {path} already exists")
            continue
        except ApiError as error:
            if error.code == 3:  # not found
                try:
                    result = ucs("config.soundfiles.mkdir", {"group_id": 1, "description": "", "path": path})
                    print(f"Created soundfile directory {path}")
                except ApiError as error:
                    print(f"unable to create soundfile directory {path}: {error}")
                    continue
            else:
                print(f"Unable to check if soundfile directory exists {path}: {error}")
                continue

        filename = soundfile["name"]
        try:
            current = ucs("config.soundfiles.get", f"{path}/{filename}")
        except ApiError as error:
            print(f"Soundfile {path}/{filename}: {error}")
            continue

        content = tar.extractfile(f"soundfiles{path}/{filename}").read()
        soundfile["content"] = base64.b64encode(content)
        soundfile["path"] = f"{path}/{filename}"
        soundfile["group_id"] = group_map.get(soundfile["group_id"], 1)
        try:
            result = ucs("config.soundfiles.add", soundfile)
            print(f"Added soundfile {path}/{filename}")
        except ApiError as error:
            print(f"Unable to add soundfile {path}/{filename}: {error}")

# TODO: settings

timeperiod_map = {}
for timeperiod in timeperiods:
    exported_timeperiod_id = timeperiod.pop("id")
    timeperiod["group_id"] = group_map.get(timeperiod["group_id"], 1)
    try:
        result = ucs("config.periods.add", timeperiod)
        print(f"""Created timeperiod {timeperiod["name"]} with ID {result}""")
        timeperiod_map[exported_timeperiod_id] = result
    except ApiError as error:
        print(f"""Unable to create timeperiod {timeperiod["name"]}: {error}""")

for extension in extensions:
    exported_extension_id = extension.pop("id")
    extension["group_id"] = group_map.get(extension["group_id"], 1)

    settings = extension["settings"]
    if extension["type"] == "timeroute":
        for route in settings["routes"]:
            if route["period_id"]:
                route["period_id"] = timeperiod_map.get(route["period_id"])

    elif extension["type"] == "queue":
        working_hours_period_id = settings["working_hours_period_id"]

        if "ivr_filter_id" in settings and settings["ivr_filter_id"] is None:
            settings.pop("ivr_filter_id")

    for k, v in list(settings.items()):
        if v is None:
            del settings[k]

    try:
        result = ucs("config.extensions.add", extension)
        print(f"""Created extension {extension["number"]} {extension["name"]} with ID {result}""")
    except ApiError as error:
        print(f"""Unable to create extension {extension["number"]} {extension["name"]}: {error}""")
