import os
import sys
from pathlib import Path
import tempfile
import json

import datetime as dt
from subprocess import call, check_output

import requests
import tomli
import humanize
import emoji as emo

from rich.console import Console
from rich.markdown import Markdown


CONFIG_DIRNAME = "omgstatus"
CONFIG_FILENAME = "config.toml"
DEFAULT_DOMAIN = "api.omg.lol"


def is_emoji(arg, config={"emoji-name-languages": ["alias"]}):
    """
    This function test if arg is an emoji.
    It support emoji via aliases in various languages.


    >>> is_emoji("🦊")
    🦊

    >>> is_emoji("qsd")
    None

    >>> is_emoji(":banana:")
    🍌

    >>> is_emoji(":babane:", {"emoji-name-languages": ["fr"]})
    🍌

    >>> is_emoji(":serpiente:", {"emoji-name-languages": ["es"]})
    🐍

    >>> is_emoji(":slop:", {"emoji-name-languages": ["es"]})
    None
    """
    if emo.is_emoji(arg):
        return arg

    # NOTE: Python is awesome
    if arg[0] == arg[-1] == ":":
        langs = (
            config["emoji-name-languages"]
            if "emoji-name-languages" in config
            else ["alias"]
        )
        if type(langs) is str:
            langs = [langs]
        for lang in langs:
            arg_emojized = emo.emojize(arg, language=lang)
            if emo.is_emoji(arg_emojized):
                return arg_emojized

    return None


def get_token(token_cmd):
    if (token_cmd[0] == "`" and token_cmd[-1] == "`") or token_cmd[0] == "$":
        token_cmd = (
            check_output(token_cmd.strip("$`").strip(), shell=True)
            .decode("utf-8")
            .rstrip("\n")
        )

    return token_cmd


def get_config():
    """
    Retrieve the config file from the user's home directory.
    Example: ~/.config/omgstatus/config.toml

    Returns:
        dict: The config file as a dict.
    """
    config_path = (
        Path(
            os.environ["XDG_CONFIG_HOME"]
            if "XDG_CONFIG_HOME" in os.environ
            else (Path(os.environ["HOME"]) / ".config")
        )
        / CONFIG_DIRNAME
        / CONFIG_FILENAME
    )
    if not config_path.exists():
        print("Config file doesn't exists.", file=sys.stderr)
        print(
            "Configure omgstatus <https://source.tube/ache/omgstatus#configuration>\n"
        )
        return {}

    with open(config_path, "rb") as f:
        return tomli.load(f)


def get_bio_status(address):
    response = requests.get(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses/bio"
    )
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        sys.exit(1)

    if "response" in data and "bio" in data["response"]:
        return data["response"]["bio"]
    else:
        return None


def show_status(status_id, address):
    response = requests.get(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses/{status_id}"
    )
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        if data["request"]["status_code"] == 404:
            return 404
        else:
            sys.exit(1)

    if "response" in data:
        status = data["response"]["status"]
    else:
        return None

    return status


def list_statuslog(since_date=None, max_number_of_status=float("inf")):
    response = requests.get(f"https://{DEFAULT_DOMAIN}/statuslog")
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        if data["request"]["status_code"]:
            return []
        else:
            sys.exit(1)

    if "response" in data:
        if "statuses" in data["response"]:
            list_statuses = data["response"]["statuses"]
        else:
            print("Error: No status found in statuslog.")
            sys.exit(1)
    else:
        print("Error: No status found in statuslog.")
        sys.exit(1)

    if since_date is not None:
        list_statuses = [
            s
            for s in list_statuses
            if dt.datetime.fromtimestamp(int(s["created"])) > since_date
        ]

    if len(list_statuses) > max_number_of_status:
        list_statuses = list_statuses[: int(max_number_of_status)]

    return list_statuses


def list_status(address, since_date=None, max_number_of_status=float("inf")):
    response = requests.get(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses"
    )
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        if data["request"]["status_code"]:
            return []
        else:
            sys.exit(1)

    if "response" in data:
        if "statuses" in data["response"]:
            list_statuses = data["response"]["statuses"]
        else:
            print(f"Error: No status found for address {address}.")
            sys.exit(1)
    else:
        # WARN: Maybe print an error and exit ?
        return []

    if since_date is not None:
        list_statuses = [
            s
            for s in list_statuses
            if dt.datetime.fromtimestamp(int(s["created"])) > since_date
        ]

    if len(list_statuses) > max_number_of_status:
        list_statuses = list_statuses[: int(max_number_of_status)]

    return list_statuses


def print_as_status(status, soft_wrap=False):
    creation_date = dt.datetime.fromtimestamp(int(status["created"]))
    print(
        f"{status['address']}: {humanize.naturaltime(dt.datetime.now() - creation_date)} @{status['id']}"
    )
    print(status["emoji"], end=" ")
    print_as_mardown(status["content"], soft_wrap=soft_wrap)


def print_as_mardown(content, soft_wrap=False):
    console = Console()
    mk = Markdown(content)
    console.print(mk, soft_wrap=soft_wrap)


def create_status(
    emoji, content, address, bearer_token, create_a_toot=False, external_url=None
):
    headers = {"Authorization": f"Bearer {get_token(bearer_token)}"}

    data_send = {
        "emoji": emoji,
        "content": content,
        "skip_mastodon_post": not create_a_toot,
    }

    if external_url is not None:
        data_send["external_url"] = external_url

    response = requests.post(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses/",
        headers=headers,
        data=json.dumps(data_send),
    )
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        sys.exit(1)
    if "response" in data:
        if "message" in data["response"]:
            print_as_mardown(data["response"]["message"])
        else:
            if "url" in data:
                print(f"OK, your status has been saved. <{data['response']['url']}>")
            else:
                print("OK, your status has been saved.")
    else:
        print("Your status has been saved but the server didn't respond correctly")


def delete_status(status_id, address, bearer_token):
    headers = {"Authorization": f"Bearer {get_token(bearer_token)}"}

    response = requests.delete(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses/{status_id}",
        headers=headers,
    )

    data = response.json()
    print(json.dumps(data, indent=4))

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        sys.exit(1)
    if "response" in data:
        if "message" in data["response"]:
            print_as_mardown(data["response"]["message"])
        else:
            if "url" in data:
                print(f"OK, your status has been deleted. <{data['response']['url']}>")
            else:
                print("OK, your status has been deleted.")
    else:
        print("Your status has been deleted but the server didn't respond correctly")


def update_status(emoji, content, status_id, address, bearer_token, external_url=None):
    # TODO: I'm note sure one can update external_url...

    headers = {"Authorization": f"Bearer {get_token(bearer_token)}"}

    data_send = {
        "id": status_id,
        "emoji": emoji,
        "content": content,
    }

    if external_url is not None:
        data_send["external_url"] = external_url

    response = requests.patch(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses/",
        headers=headers,
        data=json.dumps(data_send),
    )
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        sys.exit(1)
    if "response" in data:
        if "message" in data["response"]:
            print_as_mardown(data["response"]["message"])
        else:
            if "url" in data:
                print(f"OK, your status has been updated. <{data['response']['url']}>")
            else:
                print("OK, your status has been updated.")
    else:
        print("Your status has been updated but the server didn't respond correctly")


def update_bio(content, address, bearer_token):
    headers = {"Authorization": f"Bearer {get_token(bearer_token)}"}

    data_send = {
        "content": content,
    }

    response = requests.post(
        f"https://{DEFAULT_DOMAIN}/address/{address.encode('idna').decode('utf8')}/statuses/bio",
        headers=headers,
        data=json.dumps(data_send),
    )
    data = response.json()

    if data["request"]["status_code"] != 200:
        print(f"Error status code {data['request']['status_code']}.", file=sys.stderr)
        sys.exit(1)
    if "response" in data:
        if "message" in data["response"]:
            print_as_mardown(data["response"]["message"])
        else:
            if "url" in data:
                print(f"OK, your status has been saved. <{data['response']['url']}>")
            else:
                print("OK, your status has been saved.")
    else:
        print("Your status has been saved but the server didn't respond correctly")


def since_suffix(since_str):
    suffix = since_str[-1]
    number = since_str[:-1]
    now = dt.datetime.now()

    if not all(str.isdigit(c) for c in number):
        print(f"Error: Invalid number ({number})")
        sys.exit(1)
    number = int(number)

    match suffix:
        case "s":
            return now - dt.timedelta(seconds=number)
        case "m":
            return now - dt.timedelta(minutes=number)
        case "h" | "H":
            return now - dt.timedelta(hours=number)
        case "J" | "D" | "j" | "d":
            return now - dt.timedelta(days=number)
        case "W" | "w":
            return now - dt.timedelta(weeks=number)
        case "M":
            # BUG: Fix month to be exact
            return now - dt.timedelta(days=31 * number)
        case "Y" | "A" | "a" | "y":
            # BUG: Fix year to be exact
            return now - dt.timedelta(days=365 * number)
        case _:
            print(f"Error: Unknown suffix ({suffix}).")
            sys.exit(1)


def app():
    action = None
    status_emoji = None
    external_url = None
    content = []
    replace_address = False
    create_a_toot = False
    is_bio_related = False
    status_id = None
    should_get_last_status_id = None
    last_status = None
    should_edit_before_send = False

    # TODO: Add a filter local list
    # TODO: Fix the readme
    # TODO: Fix the code structure
    # TODO: Add an help usage string
    # TODO: Publish on pypi / AUR

    list_status_since = None
    max_number_of_status = float("inf")

    config = get_config()

    to_pass = 0
    for i, arg in enumerate(sys.argv[1:]):
        if to_pass:
            to_pass -= 1
            continue

        if tmp_ret := is_emoji(arg, config):
            status_emoji = tmp_ret
            continue

        if arg[0] == "@":
            if arg[1] == "@":
                content.append(arg[1:])
            else:
                status_id = arg[1:]
            continue

        else:
            match arg:
                case "bio":
                    is_bio_related = True
                case "--address" | "-a":
                    if not replace_address:
                        replace_address = True
                        if "address" in config:
                            # NOTE: Otherwise the token can't be correct
                            config["bearer_token"] = None

                        config["address"] = sys.argv[i + 2]
                        to_pass = 1
                    else:
                        print(
                            "Address address: Address specified multiple times.",
                            file=sys.stderr,
                        )
                        sys.exit(1)

                case "--url":
                    if create_a_toot:
                        print(
                            f"Ambiguous url: URL will be the mastodon toot incompatible with external url ({sys.argv[i + 1] + ' ' + sys.argv[i + 2]})."
                        )
                        sys.exit(1)
                    external_url = sys.argv[i + 2]
                    to_pass = 1
                case "--since" | "-s":
                    if action is not None and action not in ["list", "list_statuslog"]:
                        print("Ambiguous action: --since usable only to list statuses.")
                        sys.exit(1)

                    # NOTE: Format DD/MM/YYYY not yet supported
                    # TODO: Support format DD/MM/YYYY
                    """
                    Format should be DD/MM/YYYY, but support also suffixes:

                    | Sfx. | Value   | Lang  | Done |
                    +======+=========+=======+======+
                    | J/j  | 1 day   |  fr   |  ✅  |
                    | D/d  | 1 day   |  en   |  ✅  |
                    |  M   | 1 month | en/fr |  ✅  |
                    | A/a  | 1 year  |  fr   |  ✅  |
                    | Y/y  | 1 year  |  en   |  ✅  |
                    |  S   | 1 week  |  fr   |  ✅  |
                    | W/w  | 1 week  |  en   |  ✅  |
                    |  m   | 1 min   | fr/en |  ✅  |
                    |  s   | 1 sec   | fr/en |  ✅  |
                    | H/h  | 1 sec   | fr/en |  ✅  |
                    """

                    # BUG: If already set list_status_since, we should error
                    list_status_since = since_suffix(sys.argv[i + 2])
                    to_pass = 1

                case "--statuslog":
                    if action is not None:
                        print("Ambiguous action.")
                        sys.exit(1)
                    action = "list_statuslog"

                case "--show":
                    if action is not None:
                        print(
                            "Ambiguous action: --show usable only to show a single status."
                        )
                        sys.exit(1)
                    action = "show"

                case "--number" | "-n":
                    max_number_of_status = int(sys.argv[i + 2])
                    to_pass = 1

                case "--mastodon" | "-m" | "-🐘":
                    if external_url is not None:
                        print(
                            f"Ambiguous url: Can toot a status with external URL, the URL will be the one of the toot. Can't be ({external_url})."
                        )
                        sys.exit(1)

                    create_a_toot = True

                    # NOTE: Update seems useless
                    """
                case "--update" | "-u":
                    if (
                        action is not None
                        and action != "implied_create"
                        and action != "edit"
                    ):
                        print(
                            f"Ambiguous action: {sys.argv[i + 1]} implies to update a status, incompatible with previous action ({action})."
                        )
                        sys.exit(1)

                    if action == "edit":
                        action = "amend"
                    else:
                        action = "update"
                    status_id = sys.argv[i + 2]
                    to_pass = 1

                    """
                case "--edit" | "-e":
                    should_edit_before_send = True
                case "--delete":
                    # BUG: Shouldn't have a short option for delete
                    if action is None:
                        action = "delete"
                    else:
                        print(
                            f"Ambiguous action: {sys.argv[i + 1]} implies to delete a status, incompatible with previous action ({action})."
                        )
                        sys.exit(1)
                case "--last" | "-😱" | "-l":
                    should_get_last_status_id = True

                case _:
                    content.append(arg)

    # NOTE: List of actions:
    #  - [x] Create status
    #  - [x] Update status
    #  - [x] Update bio
    #  - [x] List general latest status # Todo
    #  - [x] List latest status of someone
    #  - [x] Show a specific status
    #  - [x] Delete a status
    #  - [x] Content of a status via stdin
    #  - [x] Edit content of a status
    #  - [x] Amend the last status (Implies via -e and -l)

    if should_get_last_status_id:
        if status_id is None:
            last_status = list_status(config["address"], max_number_of_status=1)
            if last_status:
                last_status = last_status[0]
                if "id" in last_status:
                    status_id = last_status["id"]
                else:
                    print(
                        "Error: Unable to retrieve latest status. Invalid data received.",
                        file=sys.stderr,
                    )
                    sys.exit(1)
            else:
                print(
                    "Error: Unable to retrieve latest status, no status received.",
                    file=sys.stderr,
                )
                sys.exit(1)

        else:
            print(
                "Error: You provided an status id along with --latest option to act on the last .",
                file=sys.stderr,
            )
            sys.exit(1)

    # TODO: Deals with errors

    if action is not None and action not in ["list", "list_statuslog"]:
        if list_status_since is not None:
            print(
                "Error: The since option is usable only to list the last status since a reference date.",
                file=sys.stderr,
            )
            sys.exit(1)
        if max_number_of_status < float("+inf"):
            print(
                "Error: Can only limit the number of status to show when you actually show statuses.",
                file=sys.stderr,
            )
            sys.exit(1)

    if not sys.stdin.isatty():
        if not content:
            content = [sys.stdin.read()]
        else:
            print("Ambiguous status content source. Aborting.", file=sys.stderr)
            sys.exit(1)

    if action is None:
        if status_emoji is not None and content:
            if status_id is None:
                action = "create"
            else:
                action = "update"
        elif content and is_bio_related:
            action = "update_bio"
        elif status_emoji or (content or should_edit_before_send):
            # Fill with the last status if needed !

            if last_status is not None:
                if not status_emoji:
                    status_emoji = (
                        last_status["emoji"] if "emoji" in last_status else None
                    )
                    if should_edit_before_send and not content:
                        content = (
                            [last_status["content"]] if "content" in last_status else []
                        )
                else:
                    content = (
                        [last_status["content"]] if "content" in last_status else []
                    )

            if status_emoji and content:
                action = "update"
            else:
                print(
                    "Error !!: You must provide an emoji along with the content of the status.",
                    file=sys.stderr,
                )
                sys.exit(1)
        else:
            if status_id is None:
                if is_bio_related:
                    action = "show_bio"
                else:
                    action = "list"
            else:
                if is_bio_related:
                    print(
                        f"Error: Status @{status_id} doesn't have a bio.",
                        file=sys.stderr,
                    )
                    sys.exit(1)
                else:
                    action = "show"

    if action == "list" and ("address" not in config or not config["address"]):
        action = "list_statuslog"

    if content and action not in ["create", "update", "update_bio"]:
        print(
            f"Content was send to omgstatus but the action {action} doesn't use it.",
            file=sys.stderr,
        )
        sys.exit(1)

    if status_emoji and action not in ["create", "update"]:
        print(
            f"Status emoji is set to omgstatus but the action {action} doesn't use it.",
            file=sys.stderr,
        )

    if list_status_since and action not in ["list", "list_statuslog"]:
        print(
            f"Error: The --since option doesn't make any sense with the action {action}.",
            file=sys.stderr,
        )
        sys.exit(1)

    if max_number_of_status < float("inf") and action not in ["list", "list_statuslog"]:
        print(
            f"Error: The --number option doesn't make any since with the action {action}.",
            file=sys.stderr,
        )
        sys.exit(1)

    if should_edit_before_send:
        if action not in ["create", "update", "update_bio"]:
            print(
                f"Error: Why editing the content if the action is {action} ?",
                file=sys.stderr,
            )
            # BUG: Maybe the action is wont!
            sys.exit(1)
        editor = os.environ.get("EDITOR", "vim")

        with tempfile.NamedTemporaryFile(
            prefix="omgstatus-edit-status", suffix=".tmp"
        ) as tf:
            if content:
                tf.write(" ".join(content).encode("utf-8"))
                tf.flush()

            call([editor, tf.name])
            with open(tf.name, "r") as f:
                content = [f.read()]

    match action:
        case "update_bio":
            update_bio(" ".join(content), config["address"], config["bearer_token"])
        case "create":
            if "address" not in config:
                print(
                    "Error: No address provided.",
                    file=sys.stderr,
                )
                sys.exit(1)

            if "bearer_token" not in config:
                print(
                    f"Error: No token provided for the user {config['address']}.",
                    file=sys.stderr,
                )
                sys.exit(1)
            else:
                if status_emoji is None:
                    print(
                        "Error: Emoji is needed to create a status.",
                        file=sys.stderr,
                    )
                    sys.exit(1)

                create_status(
                    status_emoji,
                    " ".join(content),
                    config["address"],
                    config["bearer_token"],
                )
        case "update":
            # TODO: The update feature
            """
            Update feature replace a status (based on its id) with another one.
            """

            if "address" not in config:
                print(
                    "Error: No address provided.",
                    file=sys.stderr,
                )
                sys.exit(1)

            if "bearer_token" not in config:
                print(
                    f"Error: No token provided for the user {config['address']}.",
                    file=sys.stderr,
                )
                sys.exit(1)

            update_status(
                status_emoji,
                " ".join(content),
                status_id,
                config["address"],
                config["bearer_token"],
            )
            print(
                f"Update status @{status_id} of {config['address']} with {status_emoji}"
            )

        case "show_bio":
            bio = get_bio_status(config["address"])
            if bio is not None:
                print_as_mardown(bio)

        case "list":
            # TODO: Add an option to list all
            statuses = list_status(
                config["address"],
                since_date=list_status_since,
                max_number_of_status=max_number_of_status,
            )
            for status in statuses[:-1]:
                print_as_status(status, soft_wrap=True)
                print()
            print_as_status(statuses[-1], soft_wrap=True)

        case "list_statuslog":
            # TODO: Add an option to list all
            statuses = list_statuslog(
                since_date=list_status_since,
                max_number_of_status=max_number_of_status,
            )
            for status in statuses[:-1]:
                print_as_status(status, soft_wrap=True)
                print()
            print_as_status(statuses[-1], soft_wrap=True)

        case "show":
            status = show_status(status_id, config["address"])
            if status == 404:
                print(f"Are you sure {status_id} is from {config['address']} ?")
            print_as_status(status, True)

        case "delete":
            if content:
                print(
                    "Error: Receive a content but the action is to delete a status.",
                    file=sys.stderr,
                )
                sys.exit(1)
            if status_emoji is not None:
                print(
                    f"Error: Receive an emoji {status_emoji} but the action is to delete a status.",
                    file=sys.stderr,
                )
                sys.exit(1)
            if status_id is None:
                print(
                    "Error: Which status are you trying to delete ? No status id specified (use -l to delete the latest one).",
                    file=sys.stderr,
                )
                sys.exit(1)

            delete_status(status_id, config["address"], config["bearer_token"])
