"""The commands of *Bankr*'s command line interface."""

import click
import os
import i18n
import re
import sys

from pathlib import Path
from datetime import datetime
from dateutil.relativedelta import relativedelta
from pyfiglet import Figlet

import bankr.app as ba
import bankr.calc as bc
import bankr.files as bf

# import bankr.tuiplot as tp
import bankr.tuitable as tt

config_path = Path("./bankr.yaml").resolve()
params = bf.read_bankr_yaml(config_path)
data_path = Path(params["DATA_PATH"]).resolve()


# Helpers


def bankr_title(subtitle: str = "") -> None:
    """_Clear CLI and print Bankr title_

    Args:
        subtitle (str, optional): _Additional subtitle_. Defaults to "".
    """

    # Clear CLI
    if os.name == "nt":  # On Windblows
        os.system("cls")
    else:  # On valuable and expensive Linux
        os.system("clear")

    # Print title
    print(Figlet().renderText("Bankr"))
    click.echo(f"### {i18n.t('general.title')} ###", nl=False)
    if subtitle:
        click.echo(f" - {subtitle}\n")
    else:
        click.echo("\n")


def bankr_info(info: str = "", **kwargs) -> None:
    """_Bankr Info_

    Print a short info, except `verbose=False`

    Args:
        info (str): _Info_. Defaults to "".
    """

    params = {"verbose": True}
    params |= kwargs

    if params["verbose"]:
        click.echo(f"Bankr {i18n.t('general.info')} - {info}")


# Bankr - Sanitizers


def sanitize_cat(cat: str = "") -> str:
    """_Sanitize the given category_

    Provides a valid `cat`, as used in `cats.yaml`, if the category information could be identified in `cats.yaml`
    or its valid translation. Multiple hits are not an identification.

    Args:
        cat (str, optional): _Category information to be sanitized_. Defaults to "".

    Returns:
        str: _Sanitized (internal) category as given in cats.yaml_
    """

    values = bf.read_bankr_yaml(data_path / "cats.yaml")
    candidates: list[str] = []
    for value in values:
        if (cat.lower() in value["cat"]) or (cat in i18n.t("cats." + value["cat"])):
            candidates.append(value["cat"])
    if len(candidates) == 1:
        return candidates[0]
    else:
        return ""


def sanitize_iban(iban: str = "", **kwargs) -> str:
    """_Sanitize a IBAN or IBAN fragment_

    The string `iban` is checked against the IBANs within `accounts.yaml`. If the IBAN could be uniquely identified,
    it is returned. Unique fragements of IBANs are sufficient.

    Args:
        iban (str, optional): _IBAN or IBAN fragment_. Defaults to "".

    Returns:
        str: _Identified IBAN or empty string_
    """

    values = bf.read_bankr_yaml(data_path / "accounts.yaml")
    candidates: list[str] = []
    for value in values:
        if iban.upper() in value["iban"]:
            candidates.append(value["iban"])
    if len(candidates) == 1:
        return candidates[0]
    else:
        return ""


def sanitize_period(period: str = "-") -> list[datetime]:
    """_Sanitize period string_

    Converts a string into a bounds list (datetime of start and end date), empty if invalid.
    YYYY-MM is a month from 1900-01 to 2099-12. YYYY is a year from 1900 to 2099.

    Args:
        period (str): _A string defining a time period_. Defaults to "-", "" not allowed.

    Returns:
        list[datetime]: _Bounds of the period, empty if invalid_
    """

    # No bounds
    bounds: list = []

    # period = month, from 1900-01 to 2099-12
    if re.match(r"^(19|20)?\d{2}-(0[1-9]|1[0-2])$", period):
        period += "-01"
        start = datetime.strptime(period, "%Y-%m-%d")
        bounds = [start, start + relativedelta(day=31)]
    # period = year, from 1900 to 2099
    if re.match(r"^(19|20)?\d{2}$", period):
        bounds = [datetime(int(period), 1, 1), datetime(int(period), 12, 31)]
    return bounds


# Bankr CLI Commands


# Helper - Test command for development
@click.command()
def test():
    click.echo("This is a command used for development only.")


@click.command()
@click.argument("csv_file")
@click.option("-a", "--add", is_flag=True, help="Add the auto-cat Page to the Book")
@click.option("-v", "--verbose", is_flag=True, help="Provide info about processing steps")
def parse(csv_file: str, add: bool, verbose: bool):
    """Parse CSV for a Page, and eventually add it to the Book.

    A summary of the Transactions of the Page, and the expectable changes of the Book are presented.
    The Book is not changed in Parse Mode. If in Add Mode, an auto categorized Page is added to the Book.
    """
    subtitle = "Parse Mode"
    if add:
        subtitle = "Add Mode"
    bankr_title(subtitle)

    # Parse CSV to Page
    page = bf.parse_csv(data_path, csv_file)
    bankr_info(f"{csv_file} {i18n.t('general.parsed')}", verbose=verbose)
    book = bf.unpickle_book(data_path)

    # Auto-categorize Page
    filtrs = bf.read_bankr_yaml(data_path / "filters.yaml")
    bc.autocat_tracts(page, filtrs)
    bankr_info(f"{i18n.t('general.page')} {i18n.t('general.categorized')}", verbose=verbose)
    if add:
        # Import Page to Book, and save it
        book = bc.add_page_to_book(page, book)
        book = bf.condition_book(book, data_path, "%d.%m.%Y")
        bankr_info(f"{i18n.t('general.page')} {i18n.t('general.imported')}", verbose=verbose)
        bf.pickle_book(book, data_path)
        bankr_info(f"{i18n.t('general.book')} {i18n.t('general.saved')}")
    else:
        # Print statistics of the relevant IBAN
        iban = csv_file.split("-")[0]
        click.echo(f"- {i18n.t('general.page')}")
        page_balance = tt.show_iban_stats(page, iban)
        click.echo(f"- {i18n.t('general.book')}")
        book_balance = tt.show_iban_stats(book, iban)
        click.echo(f"  {i18n.t('general.new_balance'):<32}{book_balance + page_balance}\n")
        while True:
            selection = input("Bankr Action - Show Transactions of the created Page (y/n)? ")  #
            if selection == "y":
                if page.shape[0] > 500:
                    bankr_info("Maximum number of Transactions is 500.")
                    break
                else:
                    tract = tt.show_page(page)
                    if tract < 0:
                        break
                    else:
                        tt.show_transaction(page, page.iloc[tract].name, True)
                        break
            elif selection == "n":
                break


@click.command()
@click.argument("iban")
def add(iban: str = "") -> None:
    """Add a Transaction to the Book."""
    bankr_title("Addition Mode")
    iban = sanitize_iban(iban)
    if not iban:
        sys.exit("Bankr Error - No IBAN given.")
    # TODO Commands add and edit have quite similar code snippets
    book = bf.unpickle_book(data_path)
    page = bf.create_transaction(iban)
    tract = str(page["uuid"][0])
    page.set_index("uuid", inplace=True)
    page = bf.condition_book(page, data_path, "%d.%m.%Y")
    book = bc.add_page_to_book(page, book)
    book = bf.condition_book(book, data_path, "%d.%m.%Y")
    tt.show_transaction(book, tract, True)
    while True:
        selection = input("Bankr Action - Add this Transaction to the Book (y/n)? ")
        if selection == "y":
            bf.pickle_book(book, data_path)
            bankr_info(f"Transaction {tract} added.")
            break
        elif selection == "n":
            bankr_info("No addition.")
            break


@click.command()
@click.argument("uuid")
def edit(uuid: str):
    """Edit a Transaction in the Book."""
    bankr_title("Edit Transaction Mode")
    book = bf.unpickle_book(data_path)
    book = bf.condition_book(book, data_path, "%d.%m.%Y")
    tract = bc.select_transaction(book, uuid)
    print("-" * 120)
    print(f"IBAN:        {tract['iban']}")
    print(f"Transaction: {tract.name}")
    print("-" * 120)
    tract = bf.edit_transaction(tract)
    page = bf.condition_book(tract.to_frame(name=tract.name).transpose(), data_path, "%d.%m.%Y")
    book = bc.delete_transaction(book, uuid)
    book = bc.add_page_to_book(page, book)
    # book = bc.update_transaction(book, page)
    tt.show_transaction(book, uuid, True)
    while True:
        selection = input("Bankr Action - Update this Transaction within the Book (y/n)? ")
        if selection == "y":
            bf.pickle_book(book, data_path)
            bankr_info(f"Transaction {uuid} updated.")
            break
        elif selection == "n":
            bankr_info(f"No update of Transaction {uuid}.")
            break


@click.command()
@click.argument("uuid")
def delete(uuid: str):
    """Delete a Transaction from the Book.

    The Transaction to delete is defined by its Transaction ID or UUID.
    """
    bankr_title("Deletion Mode")
    book = bf.unpickle_book(data_path)
    tt.show_transaction(book, uuid, True)
    while True:
        selection = input("Bankr Action - Delete this Transaction from the Book (y/n)? ")
        if selection == "y":
            book = bc.delete_transaction(book, uuid)
            bf.pickle_book(book, data_path)
            bankr_info(f"Transaction {uuid} deleted.")
            break
        elif selection == "n":
            bankr_info("No deletion.")
            break


@click.command()
@click.option("-y", "--year", type=click.IntRange(min=1900, max=2100, clamp=True), help="First year to show")
@click.option("-s", "--span", type=click.IntRange(min=1, max=10, clamp=True), help="Number of years to show")
@click.option("-q", "--quarter", is_flag=True, help="Show quarters")
@click.option("-t", "--term", is_flag=True, help="Show terms/half-years")
@click.option("-f", "--full", is_flag=True, help="Show full years")
def stats(year: int, span: int, quarter: bool, term: bool, full: bool):
    """Statistics of the Book or per time interval.

    Statistics of the Book:
    The number of transactions per IBAN, its current account balance, and the date of the
    first/last transaction are shown.

    Transaction Statistics:
    Amounts per category and time interval.
    """
    book = bf.unpickle_book(data_path)
    if not span:
        span = 200
    period = "m"
    if quarter:
        period = "q"
    if term:
        period = "t"
    if full:
        period = "f"
    if year:  # Transaction Statistics
        bankr_title(i18n.t("general.statistics"))
        amounts = bc.sum_per_cat_period(book, year, span, period=period, pivot=True)
        amounts = bc.append_totals(amounts)
        tt.show_income_per_interval(amounts)
    else:  # Statistics of the Book
        bankr_title(f"{i18n.t('general.book_stats')}")
        accounts = bf.read_bankr_yaml(data_path / "accounts.yaml")
        extended_accounts: list[dict] = bc.calc_book_stats(book, accounts)
        tt.show_book_stats(extended_accounts)


@click.command()
@click.option("-m", "--manual", is_flag=True, help="Manually categorize Transactions without category")
@click.option("-u", "--uuid", type=click.STRING, help="Manually categorize a Transaction with a given ID")
def cat(manual: bool, uuid: str):
    """Categorize Transactions of a Page or the Book.

    Basis of the automatic categorization is `filters.yaml`. Manual and auto cat work only
    on transactions without a category.
    """
    book = bf.unpickle_book(data_path)
    if uuid and bc.tract_existing(book, uuid):
        cats = bf.read_bankr_yaml(data_path / "cats.yaml")
        bankr_title("TractID Mode")
        tt.show_transaction(book, uuid, True)
        selection = bc.select_category(cats)
        if selection != "quit":
            book.loc[uuid, "cat"] = selection
        bankr_info(f"{i18n.t('general.uuid')} {uuid} {i18n.t('general.categorized')}")
    elif uuid and not bc.tract_existing(book, uuid):
        click.echo("Bankr Error - Invalid Transaction ID")
        return
    elif manual:
        cats = bf.read_bankr_yaml(data_path / "cats.yaml")
        bankr_title("Manual Mode")
        bc.mancat_tracts(book, cats)
        bankr_info(
            f"{i18n.t('general.book')} {i18n.t('general.categorized')} - {sum(book['cat'] == 'none')} {i18n.t('general.wo_cat')}"
        )
    else:
        filtrs = bf.read_bankr_yaml(data_path / "filters.yaml")
        bankr_title("Auto Mode")
        bc.autocat_tracts(book, filtrs)  # noqa: F823
        bankr_info(
            f"{i18n.t('general.book')} {i18n.t('general.categorized')} - {sum(book['cat'] == 'none')} {i18n.t('general.wo_cat')}"
        )
    bf.pickle_book(book, data_path)
    bankr_info(f"{i18n.t('general.book')} {i18n.t('general.saved')}")


@click.command()
@click.option("-p", "--period", default="", type=click.STRING, help="Period to show")
@click.option("-c", "--cat", default="", type=click.STRING, help="Category to show")
@click.option("-i", "--iban", default="", type=click.STRING, help="IBAN to show")
@click.option("-x", "--check_external", is_flag=True, help="Check if offset is external")
def show(period: str, cat: str, iban: str, check_external: bool):
    """Show the Transactions of an interval or an IBAN.

    Options restrict the displayed Transactions to the given time interval (month or year),
    or limit these to an IBAN.
    """

    # Sanitize parameters
    bounds = sanitize_period(period)
    cat = sanitize_cat(cat)
    iban = sanitize_iban(iban)

    # Collect internal IBANs
    ibans = []
    if check_external:
        accounts = bf.read_bankr_yaml(data_path / "accounts.yaml")
        ibans = [account["iban"] for account in accounts]

    # Open and condition Book
    book = bf.unpickle_book(data_path)
    book = bf.condition_book(book, data_path, "%d.%m.%Y")
    bankr_title("Page Mode")

    # Extract and show Page
    page = bc.extract_page_from_book(book, bounds=bounds, cat=cat, iban=iban, offsets=ibans)
    tt.show_page_header(page.shape[0], page["cents"].sum(), bounds=bounds, cat=cat, iban=iban)
    if page.shape[0] > 500:
        bankr_info("Maximum number of Transactions is 500.")
    else:
        tract = tt.show_page(page)
        if tract < 0:
            return
        else:
            tt.show_transaction(page, page.iloc[tract].name, True)


# @click.command()
# @click.option("-y", "--year", type=click.IntRange(min=1900, max=2100, clamp=True), help="First year to plot")
# @click.option("-s", "--span", type=click.IntRange(min=1, max=10, clamp=True), help="Number of years to plot")
# @click.option("-q", "--quarter", is_flag=True, help="Plot quarters")
# @click.option("-t", "--term", is_flag=True, help="Plot terms/half-years")
# @click.option("-f", "--full", is_flag=True, help="Plot full years")
# def plot(year: int, span: int, quarter: bool, term: bool, full: bool):
#     """NOT OPERATIONAL for v0.0.3!

#     Plot time information on CLI.

#     Plot the amounts within a category per time interval on CLI. The time interval
#     is a month, a quarter, a term or half-year, or a year. When ambiguous, larger
#     intervals win.

#     Limitations: Very small fractions of the total amount can not be plotted on CLI.
#     Therefore, it can only provide a first impression of the actual distribution.
#     """
#     book = bf.unpickle_book(data_path)
#     if not year:
#         year = 0
#     if not span and year:
#         span = 1
#     amounts = bc.calc_cat_per_interval(book, year, span, quarter=quarter, term=term, full=full)
#     plot_title = i18n.t("general.monthly")
#     if quarter:
#         plot_title = i18n.t("general.quarterly")
#     if term:
#         plot_title = i18n.t("general.per_term")
#     if full:
#         plot_title = i18n.t("general.yearly")
#     bankr_title("")
#     tp.plot_cat_per_interval(amounts, plot_title)


# @click.command()
# @click.argument("transfer", type=click.Choice(["import", "export"]))
# def excel(transfer: str):
#     """Import/export the Book from/into an Excel file (not valid in v0.0.1!).

#     Limitation: The Excel version of the Book has no Excel data formating, and it uses the internal data
#     representation (no i18n).
#     """
#     if transfer == "import":
#         bankr_title("Excel Import")
#         book = bf.unexcel_book(data_path)
#         book = bf.condition_book(book, data_path, "%d.%m.%Y")
#         bankr_info(f"{i18n.t('general.book')} {i18n.t('general.imported')} {i18n.t('general.from_excel')}")
#         bf.pickle_book(book, data_path)
#         bankr_info(f"{i18n.t('general.book')} {i18n.t('general.saved')}")
#     else:
#         bankr_title("Excel Export")
#         book = bf.unpickle_book(data_path)
#         bf.excel_book(book, data_path)
#         bankr_info(f"{i18n.t('general.book')} {i18n.t('general.exported')} {i18n.t('general.to_excel')}")


@click.command()
def new():
    """Create a new Book in DATA_PATH, see `bankr.yaml`.

    Creates an almost empty Book, which contains a zero value "Initial Transaction" for each
    account in `accounts.yaml`.
    """
    bankr_title("New Book Mode")
    bf.create_new_book(data_path)
    bankr_info("New Book created! Return to a backup, if processed erroneously.")


# v0.0.3 Just a mock up right now
@click.command()
@click.argument("cmd", default="start")
def app(cmd: str) -> None:
    """_Start or stop the Bankr App._

    Args:
        cmd (str): _`start` (default) or `stop` the App_
    """
    if cmd.lower() == "start":
        bankr_info("Starting App...")
        ba.start_app()
    elif cmd.lower() == "stop":
        ba.stop_app()
        bankr_info("App stopped")
    else:
        bankr_info("Bankr does not understand")
