import json as pyjson
from typing import Optional

import click

from toot import api, config
from toot.cli import Context, cli, json_option, pass_context
from toot.entities import List, from_dict_list
from toot.output import print_list_accounts, print_lists, print_warning


@cli.group(invoke_without_command=True)
@click.pass_context
def lists(ctx: click.Context):
    """Display and manage lists"""
    if ctx.invoked_subcommand is None:
        print_warning("`toot lists` is deprecated in favour of `toot lists list`.\n" +
                      "Run `toot lists -h` to see other list-related commands.")

        user, app = config.get_active_user_app()
        if not user or not app:
            raise click.ClickException("This command requires you to be logged in.")

        data = api.get_lists(app, user)
        lists = from_dict_list(List, data)
        if lists:
            print_lists(lists)
        else:
            click.echo("You have no lists defined.")


@lists.command()
@json_option
@pass_context
def list(ctx: Context, json: bool):
    """List all your lists"""
    data = api.get_lists(ctx.app, ctx.user)

    if json:
        click.echo(pyjson.dumps(data))
    else:
        if data:
            lists = from_dict_list(List, data)
            print_lists(lists)
        else:
            click.echo("You have no lists defined.")


@lists.command()
@click.argument("title", required=False)
@click.option("--id", help="List ID if not title is given")
@json_option
@pass_context
def accounts(ctx: Context, title: str, id: Optional[str], json: bool):
    """List the accounts in a list"""
    list_id = _get_list_id(ctx, title, id)
    response = api.get_list_accounts(ctx.app, ctx.user, list_id)

    if json:
        click.echo(pyjson.dumps(response))
    else:
        print_list_accounts(response)


@lists.command()
@click.argument("title")
@click.option(
    "--replies-policy",
    type=click.Choice(["followed", "list", "none"]),
    default="none",
    help="Replies policy"
)
@json_option
@pass_context
def create(ctx: Context, title: str, replies_policy: str, json: bool):
    """Create a list"""
    response = api.create_list(ctx.app, ctx.user, title=title, replies_policy=replies_policy)
    if json:
        print(response.text)
    else:
        click.secho(f"✓ List \"{title}\" created.", fg="green")


@lists.command()
@click.argument("title", required=False)
@click.option("--id", help="List ID if not title is given")
@json_option
@pass_context
def delete(ctx: Context, title: str, id: Optional[str], json: bool):
    """Delete a list"""
    list_id = _get_list_id(ctx, title, id)
    response = api.delete_list(ctx.app, ctx.user, list_id)
    if json:
        click.echo(response.text)
    else:
        click.secho(f"✓ List \"{title if title else id}\" deleted.", fg="green")


@lists.command()
@click.argument("title", required=False)
@click.argument("account")
@click.option("--id", help="List ID if not title is given")
@json_option
@pass_context
def add(ctx: Context, title: str, account: str, id: Optional[str], json: bool):
    """Add an account to a list"""
    list_id = _get_list_id(ctx, title, id)
    found_account = api.find_account(ctx.app, ctx.user, account)

    try:
        response = api.add_accounts_to_list(ctx.app, ctx.user, list_id, [found_account["id"]])
        if json:
            click.echo(response.text)
        else:
            click.secho(f"✓ Added account \"{account}\"", fg="green")
    except Exception:
        # TODO: this is slow, improve
        # if we failed to add the account, try to give a
        # more specific error message than "record not found"
        my_accounts = api.followers(ctx.app, ctx.user, found_account["id"])
        found = False
        if my_accounts:
            for my_account in my_accounts:
                if my_account["id"] == found_account["id"]:
                    found = True
                    break
        if found is False:
            raise click.ClickException(f"You must follow @{account} before adding this account to a list.")
        raise


@lists.command()
@click.argument("title", required=False)
@click.argument("account")
@click.option("--id", help="List ID if not title is given")
@json_option
@pass_context
def remove(ctx: Context, title: str, account: str, id: Optional[str], json: bool):
    """Remove an account from a list"""
    list_id = _get_list_id(ctx, title, id)
    found_account = api.find_account(ctx.app, ctx.user, account)
    response = api.remove_accounts_from_list(ctx.app, ctx.user, list_id, [found_account["id"]])
    if json:
        click.echo(response.text)
    else:
        click.secho(f"✓ Removed account \"{account}\"", fg="green")


# -- Deprecated commands -------------------------------------------------------


@cli.command(name="list_accounts", hidden=True)
@click.argument("title", required=False)
@click.option("--id", help="List ID if not title is given")
@pass_context
def list_accounts(ctx: Context, title: str, id: Optional[str]):
    """List the accounts in a list"""
    print_warning("`toot list_accounts` is deprecated in favour of `toot lists accounts`")
    list_id = _get_list_id(ctx, title, id)
    response = api.get_list_accounts(ctx.app, ctx.user, list_id)
    print_list_accounts(response)


@cli.command(name="list_create", hidden=True)
@click.argument("title")
@click.option(
    "--replies-policy",
    type=click.Choice(["followed", "list", "none"]),
    default="none",
    help="Replies policy"
)
@pass_context
def list_create(ctx: Context, title: str, replies_policy: str):
    """Create a list"""
    print_warning("`toot list_create` is deprecated in favour of `toot lists create`")
    api.create_list(ctx.app, ctx.user, title=title, replies_policy=replies_policy)
    click.secho(f"✓ List \"{title}\" created.", fg="green")


@cli.command(name="list_delete", hidden=True)
@click.argument("title", required=False)
@click.option("--id", help="List ID if not title is given")
@pass_context
def list_delete(ctx: Context, title: str, id: Optional[str]):
    """Delete a list"""
    print_warning("`toot list_delete` is deprecated in favour of `toot lists delete`")
    list_id = _get_list_id(ctx, title, id)
    api.delete_list(ctx.app, ctx.user, list_id)
    click.secho(f"✓ List \"{title if title else id}\" deleted.", fg="green")


@cli.command(name="list_add", hidden=True)
@click.argument("title", required=False)
@click.argument("account")
@click.option("--id", help="List ID if not title is given")
@pass_context
def list_add(ctx: Context, title: str, account: str, id: Optional[str]):
    """Add an account to a list"""
    print_warning("`toot list_add` is deprecated in favour of `toot lists add`")
    list_id = _get_list_id(ctx, title, id)
    found_account = api.find_account(ctx.app, ctx.user, account)

    try:
        api.add_accounts_to_list(ctx.app, ctx.user, list_id, [found_account["id"]])
    except Exception:
        # if we failed to add the account, try to give a
        # more specific error message than "record not found"
        my_accounts = api.followers(ctx.app, ctx.user, found_account["id"])
        found = False
        if my_accounts:
            for my_account in my_accounts:
                if my_account["id"] == found_account["id"]:
                    found = True
                    break
        if found is False:
            raise click.ClickException(f"You must follow @{account} before adding this account to a list.")
        raise

    click.secho(f"✓ Added account \"{account}\"", fg="green")


@cli.command(name="list_remove", hidden=True)
@click.argument("title", required=False)
@click.argument("account")
@click.option("--id", help="List ID if not title is given")
@pass_context
def list_remove(ctx: Context, title: Optional[str], account: str, id: Optional[str]):
    """Remove an account from a list"""
    print_warning("`toot list_remove` is deprecated in favour of `toot lists remove`")
    list_id = _get_list_id(ctx, title, id)
    found_account = api.find_account(ctx.app, ctx.user, account)
    api.remove_accounts_from_list(ctx.app, ctx.user, list_id, [found_account["id"]])
    click.secho(f"✓ Removed account \"{account}\"", fg="green")


def _get_list_id(ctx: Context, title: Optional[str], list_id: Optional[str]):
    if not list_id and not title:
        raise click.ClickException("Please specify list title or ID")

    lists = api.get_lists(ctx.app, ctx.user)
    matched_ids = [list["id"] for list in lists if _matches(list, title, list_id)]

    if not matched_ids:
        raise click.ClickException("List not found")

    if len(matched_ids) > 1:
        raise click.ClickException("Found multiple lists with the same title, please specify the ID instead")

    return matched_ids[0]


def _matches(list, title, list_id):
    return (title and title.lower() == list["title"].lower()) or (list_id and list_id == list["id"])
