#!/usr/bin/env python
# by Dominik Stanisław Suchora <hexderm@gmail.com>
# License: GNU GPLv3

import os
import sys
import argparse
import json
import re

import treerequests

from .api import Api


def valid_password(password):
    if len(password) == 0:
        return password

    try:
        Api._valid_password(password)
    except Exception:
        raise argparse.ArgumentTypeError(f'Incorrect password - "{password}"')

    return password


def valid_key(key):
    if len(key) != 44:
        raise argparse.ArgumentTypeError(f'Incorrect key - "{key}"')
    return key


def valid_uuid(uuid):
    if not re.fullmatch(r"^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", uuid):
        raise argparse.ArgumentTypeError(f'Incorrect uuid - "{uuid}"')
    return uuid


def valid_source(source):
    source = source.lower()
    if source not in ("swish", "paypal", "card", "other"):
        raise argparse.ArgumentTypeError(f'Incorrect source - "{source}"')
    return source


def valid_token(token):
    if len(token) <= 5:
        raise argparse.ArgumentTypeError(f'Incorrect token - "{token}"')
    return token


def valid_device(device):
    def err():
        raise argparse.ArgumentTypeError(f'Incorrect device - "{device}"')

    try:
        device = json.loads(device)
    except Exception:
        err()

    if not isinstance(device, dict):
        err()

    for i in (
        "id",
        "name",
        "pubkey",
        "hijack_dns",
        "created",
        "ipv4_address",
        "ipv6_address",
        "ports",
    ):
        if i not in device:
            err()

    return device


def valid_instance(instance):
    def err():
        raise argparse.ArgumentTypeError(f'Incorrect instance - "{instance}"')

    try:
        instance = json.loads(instance)
    except Exception:
        err()

    if not isinstance(instance, dict):
        err()

    for i in (
        "hostname",
        "country_code",
        "country_name",
        "city_code",
        "city_name",
        "fqdn",
        "active",
        "owned",
        "provider",
        "ipv4_addr_in",
        "ipv6_addr_in",
        "network_port_speed",
        "stboot",
        "type",
        "status_messages",
        "pubkey",
        "multihop_port",
        "socks_name",
        "socks_port",
        "daita",
    ):
        if i not in instance:
            err()

    return instance


def cmd_create_account(api, args):
    try:
        print(api.create_account())
    except Exception as e:
        print("Couldn't create accounts - {}".format(repr(e)), file=sys.stderr)


def cmd_version(api, args):
    print(api.version())


def cmd_instances(api, args):
    print(json.dumps(api.instances(), indent=2))


def cmd_devices(api, args):
    print(json.dumps(api.devices(), indent=2))


def cmd_status(api, args):
    print(json.dumps(api.status(), indent=2))


def cmd_create_device(api, args):
    print(json.dumps(api.create_device(args.key), indent=2))


def cmd_revoke_device(api, args):
    api.revoke_device(args.uuid)


def cmd_receipt(api, args):
    print(json.dumps(api.receipt(args.source, args.token), indent=2))


def cmd_create_config(api, args):
    sys.stdout.write(api.create_config(args.instance, args.device))


def argparser():
    parser = argparse.ArgumentParser(
        description="Tool for interacting with mullvad.net\n\nIf password is not passed through arguments, it'll be read from $MULLVAD_PASSWORD environment variable.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        add_help=False,
    )

    general = parser.add_argument_group("General")
    general.add_argument(
        "-h",
        "--help",
        action="help",
        help="Show this help message and exit",
    )
    general.add_argument(
        "-p",
        "--password",
        metavar="PASSWORD",
        type=valid_password,
        default=os.environ.get("MULLVAD_PASSWORD", ""),
        help="Specify password for authentication",
    )

    subparsers = parser.add_subparsers(title="subcommands", required=True)

    create_account_parser = subparsers.add_parser(
        "create-account", help="create account", description="create account"
    )
    create_account_parser.set_defaults(func=cmd_create_account)

    status_parser = subparsers.add_parser(
        "status", help="Get status", description="Get status"
    )
    status_parser.set_defaults(func=cmd_status)

    devices_parser = subparsers.add_parser(
        "devices", help="Get devices", description="Get devices"
    )
    devices_parser.set_defaults(func=cmd_devices)

    instances_parser = subparsers.add_parser(
        "instances", help="Get instances", description="Get instances"
    )
    instances_parser.set_defaults(func=cmd_instances)

    version_parser = subparsers.add_parser(
        "version", help="Get version", description="Get version"
    )
    version_parser.set_defaults(func=cmd_version)

    create_device_parser = subparsers.add_parser(
        "create-device",
        help="create new device",
        description="create new device with wireguard key",
    )
    create_device_parser.add_argument("key", metavar="KEY", type=valid_key)
    create_device_parser.set_defaults(func=cmd_create_device)

    revoke_device_parser = subparsers.add_parser(
        "revoke-device",
        help="remove device",
        description="remove device",
    )
    revoke_device_parser.add_argument("uuid", metavar="UUID", type=valid_uuid)
    revoke_device_parser.set_defaults(func=cmd_revoke_device)

    receipt_parser = subparsers.add_parser(
        "receipt",
        help="Get receipt",
        description="Get receipt",
    )
    receipt_parser.add_argument("source", metavar="SOURCE", type=valid_source)
    receipt_parser.add_argument("token", metavar="TOKEN", type=valid_token)
    receipt_parser.set_defaults(func=cmd_receipt)

    create_config_parser = subparsers.add_parser(
        "create-config",
        help="create wireguird config",
        description="create wireguird config",
    )
    create_config_parser.add_argument(
        "instance", metavar="INSTANCE", type=valid_instance
    )
    create_config_parser.add_argument("device", metavar="DEVICE", type=valid_device)
    create_config_parser.set_defaults(func=cmd_create_config)

    treerequests.args_section(parser)

    return parser


def cli(argv: list[str]):
    args = argparser().parse_args(argv)

    api = Api()
    treerequests.args_session(api.ses, args)

    try:
        if len(args.password) > 0:
            api.login(args.password)
        args.func(api, args)
    except Exception as e:
        print(repr(e), file=sys.stderr)
