package me.teridax.jcash.banking;

import me.teridax.jcash.Logging;
import me.teridax.jcash.banking.accounts.Account;
import me.teridax.jcash.banking.accounts.Owner;
import me.teridax.jcash.banking.management.Profile;
import me.teridax.jcash.gui.Utils;

import java.util.*;
import java.util.regex.Pattern;

/**
 * Bank storing a name, blz and a list of owners and their accounts for this bank.
 */
@SuppressWarnings("unused")
public final class Bank {
    /**
     * The name of the bank
     */
    private final String name;

    /**
     * Bankleitzahl. Identification number of banks in germany
     */
    private final String blz;

    /**
     * A map of banking accounts associated with their respective owner
     */
    private final Map<Owner, Set<Account>> accounts;

    public Bank(String blz, String name) {
        this.blz = blz;
        this.name = name;
        this.accounts = new HashMap<>();
    }

    /**
     * Validates the given BLZ. If the blz is not valid an exception is thrown.
     *
     * @param maybeBlz a string to be tested for being a blz
     * @return returns the correctly formatted blz
     * @throws IllegalArgumentException if the supplied argument is not a valid blz
     */
    public static String validateBlz(String maybeBlz) throws IllegalArgumentException {
        var pattern = Pattern.compile("[\\w-_]+");
        var matcher = pattern.matcher(maybeBlz);

        if (matcher.find())
            return matcher.group();

        throw new IllegalArgumentException("not a valid BLZ: " + maybeBlz);
    }

    public String getBlz() {
        return blz;
    }

    public String getName() {
        return name;
    }

    /**
     * Add a new account and its associated owner to this bank.
     * If the owner is already present the account is added to the existing owner.
     * If the account is already present for the owner the old account gets overwritten.
     *
     * @param owner   the owner of the account
     * @param account the account of the owner
     */
    public void addAccount(Owner owner, Account account) {
        var set = this.accounts.getOrDefault(owner, new HashSet<>());
        set.add(account);
        this.accounts.put(owner, set);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Bank) {
            return Objects.equals(blz, ((Bank) obj).blz);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(blz);
    }

    /**
     * Retrieve all accounts owned by the specific owner.
     *
     * @param owner the owner for which accounts are to be retrieved
     * @return all accounts owned by the owner and managed by this bank
     */
    public Account[] getAccountsOfOwner(Owner owner) {
        return accounts.get(owner).toArray(Account[]::new);
    }

    /**
     * Return a profile of the specified international bank account number.
     *
     * @param iban the number to create a profile for
     * @return the profile for the iban account
     */
    public Optional<Profile> getAccount(int iban) {
        // find the account which has the supplied iban

        for (var owner : this.accounts.entrySet()) {
            for (var account : owner.getValue()) {
                if (iban == account.getIban()) {
                    return Optional.of(new Profile(owner.getKey(), this, account, getAccountsOfOwner(owner.getKey())));
                }
            }
        }
        Logging.LOGGER.finer("Account not found: " + iban);
        return Optional.empty();
    }
}
