#!/usr/bin/env python3
import csv
import dataclasses
import datetime
import decimal
import enum
import pathlib
import random
import re
import string
import sys
from typing import Any

import click
import jinja2
import pytz

from cde_sepa import validation
from cde_sepa.util import cdedbid, parse_amount, parse_id


def now() -> datetime.datetime:
    """Return the current timezone-aware time."""
    return datetime.datetime.now(pytz.timezone("Europe/Berlin"))


def make_random_id() -> str:
    """Returns a pseudo-random unique identifier for use as a End-To-End-Reference."""
    timestamp = now().timestamp()
    filler = "".join(random.sample(string.ascii_letters + string.digits, 10))
    return f"{timestamp:.6f}-{filler}"


@dataclasses.dataclass
class Transaction:
    uuid: str
    date: datetime.date
    amount: decimal.Decimal
    persona_id: int | None
    family_name: str
    given_names: str
    sender_account: str
    reference: str
    iban: str

    @property
    def account_holder(self) -> str:
        return f"{self.given_names} {self.family_name}".strip()

    @property
    def negative_amount_german(self) -> str:
        return f"{-self.amount:,.2f}".replace(",", "").replace(".", ",")

    @property
    def sepa_reference(self) -> str:
        ret = self.reference
        if not ret:
            return ""
        if self.persona_id:
            ret += ", " + f"{self.given_names} {self.family_name} ({cdedbid(self.persona_id)})".strip()
        return ret

    def to_sepa(self) -> dict[str, Any]:
        return {
            "unique_id": self.uuid,
            "amount": self.amount,
            "account_owner": self.account_holder,
            "iban": self.iban,
            "reference": self.sepa_reference,
        }


class IBAN(enum.Enum):
    Skatbank = "DE23830654080005374499"
    Sozialbank = "DE26370205000008068900"


date_help = (
    "Date on which the transactions should be executed. Defaults to current day."
    " The bank should postpone this to the next valid day automatically."
)


@click.command(help="Create a SEPA-file from CSV.")
@click.argument("csvfile", type=click.Path(exists=True, path_type=pathlib.Path), required=True)
@click.option("--date", type=click.DateTime(), help=date_help)
@click.option("--account", type=click.Choice(IBAN), default=IBAN.Skatbank, help="Which account to use.")
def main(*, csvfile: pathlib.Path, date: datetime.date | str | None = None, account: IBAN) -> int | None:
    if isinstance(date, datetime.datetime):
        date = date.date()
    if isinstance(date, str):
        date = datetime.date.fromisoformat(date)
    if date is None:
        date = now().date()
    sender_iban = account.value

    working_dir = csvfile.parent
    outfile = working_dir / f"{csvfile.stem}-transactions.xml"
    if outfile.exists():
        print(f"File '{outfile}' already exists. Overwrite (y/n)?")
        if input().lower() not in {"y", "yes", "j", "ja"}:
            print("Aborting...")
            raise FileExistsError(outfile)

    # Validate input
    transactions = []
    with csvfile.open(encoding="utf-8-sig") as infile:
        # We expect the input to have a header line.
        reader = csv.DictReader(infile, delimiter=";", quotechar='"')
        for i, line in enumerate(reader):

            def get(*keys: str) -> str:
                for key in keys:
                    if key in line:
                        return line[key]
                return ""

            t = Transaction(
                uuid=make_random_id(),
                date=date,
                amount=parse_amount(get("Betrag")) or -(parse_amount(get("reg.remaining_owed")) or decimal.Decimal(0)),
                persona_id=parse_id(get("persona.id", "persona.cdedbid")),
                family_name=get("persona.family_name", "Nachname", "Name", "Kontoinhaber"),
                given_names=get("persona.given_names", "Vorname(n)", "Vorname"),
                sender_account=sender_iban if not get("intern") else "",
                reference=get("Verwendungszweck", "reference"),
                iban=re.sub(r"\s", "", get("iban", "IBAN")),
            )

            if not t.account_holder:
                print(f"No account owner information found for line {i + 1}, skipping.")
                continue

            if not t.amount:
                print(f"Could not convert {line['Betrag']!r} (line {i + 1}) to Decimal, skipping.")
                continue

            if not t.iban:
                print(f"No IBAN found for line {i + 1}, skipping.")
                continue

            if not t.reference:
                print(f"No Reference found for line {i + 1}.")

            transactions.append(t)

    sepa_transactions, errors = validation.check_transactions([t.to_sepa() for t in transactions if t.sender_account])
    internal_transactions = [t for t in transactions if not t.sender_account]
    if errors:
        print("Invalid input:")
        for error in errors:
            print(validation.print_error(error))
        return -1
    elif not sepa_transactions:
        print("No transactions found!")
        return -1
    elif len(sepa_transactions) + len(internal_transactions) != i + 1:
        print("Some lines were skipped due to erros.")
        return -1

    if date < now().date():
        print("Payment date mustn't be in the past.")
        date = now().date()
    meta = {
        "sender_name": "Club der Ehemaligen der deutschen SchülerAkademien e.V.",
        "sender_iban": sender_iban,
        "payment_date": date,
        "message_id": make_random_id(),
        "control_sum": sum(t["amount"] for t in sepa_transactions),
    }

    # Render output
    jinja_env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(str(pathlib.Path(__file__).parent.joinpath("templates"))), autoescape=False
    )
    jinja_env.globals.update({"now": now})
    template = jinja_env.get_template("pain.001.003.03.tmpl")

    with open(outfile, mode="w", encoding="utf8") as f:
        f.write(template.render({"meta": meta, "transactions": sepa_transactions}))

    print(f"Success! Wrote {len(transactions)} transactions to file {outfile}. Total: {meta['control_sum']} EUR.")
    if internal_transactions:
        print(
            f"Skipped {len(internal_transactions)} internal transactions totaling"
            f" {sum(t.amount for t in internal_transactions)} EUR"
            f" for an overall total of {sum(t.amount for t in transactions)} EUR."
        )

    return None


if __name__ == "__main__":
    sys.exit(main())
