from django.db.models import Sum

from djmoney.money import Money
from datetime import timedelta

import json
import re

from .models import get_default_currency, TransactionJournal, Transaction, Account
from .utilities import get_currency


def get_color_code(i):
    color_codes = ["98, 181, 229", "134, 188, 37", "152, 38, 73", "124, 132, 131", "213, 216, 135", "247, 174, 248"]
    color = (i - 1) % len(color_codes)

    return color_codes[color]


class Chart:
    def __init__(self, data):
        self.data = data

    def generate_json(self):
        chart_data = self._generate_chart_data()
        chart_options = self._generate_chart_options()
        chart_data["options"] = chart_options

        return json.dumps(chart_data).replace('"<<', "").replace('>>"', "")

    def _generate_chart_data(self):
        raise NotImplementedError

    def _generate_chart_options(self):
        return self._get_default_options()

    def _get_default_options(self):
        return {
            "maintainAspectRatio": False,
            "legend": {
                "display": False,
            },
            "animation": {"duration": 0},
            "responsive": True,
            "tooltips": {
                "backgroundColor": "#f5f5f5",
                "titleFontColor": "#333",
                "bodyFontColor": "#666",
                "bodySpacing": 4,
                "xPadding": 12,
                "mode": "nearest",
                "intersect": 0,
                "position": "nearest",
            },
            "scales": {
                "yAxes": [
                    {
                        "barPercentage": 1.6,
                        "gridLines": {"drawBorder": False, "color": "rgba(29,140,248,0.0)", "zeroLineColor": "transparent"},
                        "ticks": {"padding": 20, "fontColor": "#9a9a9a"},
                    }
                ],
                "xAxes": [
                    {
                        "type": "time",
                        "time": {"unit": "day"},
                        "barPercentage": 1.6,
                        "gridLines": {"drawBorder": False, "color": "rgba(225,78,202,0.1)", "zeroLineColor": "transparent"},
                        "ticks": {"padding": 20, "fontColor": "#9a9a9a"},
                    }
                ],
            },
        }


class AccountChart(Chart):
    def __init__(self, data, accounts, start_date, end_date, user=None, *args, **kwargs):
        self.start_date = start_date
        self.end_date = end_date
        self.accounts = accounts
        self.user = user
        self.currency = get_default_currency(user=self.user)

        super().__init__(data=data, *args, **kwargs)

    def _generate_chart_options(self):
        options = self._get_default_options()

        options["tooltips"]["callbacks"] = {
            "label": "<<function(tooltipItems, data) { return data.datasets[tooltipItems.datasetIndex].label + ': ' + tooltipItems.yLabel + ' (%s)'; }>>"
            % get_currency(self.currency, self.user)
        }

        if abs((self.end_date - self.start_date).days) > 150:
            options["scales"]["xAxes"][0]["time"]["unit"] = "week"

        return options

    def _generate_chart_data(self):
        dates = []

        for days_to_add in range(abs((self.end_date - self.start_date).days) + 1):
            day = self.start_date + timedelta(days=days_to_add)
            dates.append(day)

        data = {"type": "line", "data": {"labels": [date.strftime("%d %b %Y") for date in dates], "datasets": []}}

        accounts = {}
        accounts_virtual_balance = {}

        for item in self.data:
            if str(item.amount.currency) == str(self.currency) and item.account is not None:
                account_key = "{type} - {account}".format(type=item.account.get_type_display(), account=item.account.name)

                if account_key in accounts.keys():
                    date_entry = accounts[account_key].get(item.journal.date, Money(0, self.currency))
                    date_entry += item.amount

                    accounts[account_key][item.journal.date] = date_entry

                else:
                    accounts[account_key] = {item.journal.date: item.amount}
                    accounts_virtual_balance[account_key] = item.account.virtual_balance

        for account in self.accounts:
            account_key = "{type} - {account}".format(type=account.get_type_display(), account=account.name)

            if account_key not in accounts.keys():
                accounts[account_key] = {}

        counter = 1
        for account, date_entries in accounts.items():
            color = get_color_code(counter)

            account_data = {
                "label": account,
                "fill": "!1",
                "borderColor": "rgba({color}, 1.0)".format(color=color),
                "borderWidth": 2,
                "borderDash": [],
                "borderDash0ffset": 0,
                "pointBackgroundColor": "rgba({color}, 1.0)".format(color=color),
                "pointBorderColor": "rgba(255,255,255,0)",
                "pointHoverBackgroundColor": "rgba({color}, 1.0)".format(color=color),
                "pointBorderWidth": 20,
                "pointHoverRadius": 4,
                "pointHoverBorderWidth": 15,
                "pointRadius": 4,
                "data": [],
            }

            if abs((self.end_date - self.start_date).days) > 150:
                account_data["pointRadius"] = 0

            counter += 1

            for date_index in range(len(dates)):
                date = dates[date_index]

                value = 0

                if date_index == 0:
                    ACCOUNT_REGEX = re.compile(r"(.*)\s-\s(.*)")

                    account_name = ACCOUNT_REGEX.match(account)[2]
                    account_type = Account.AccountType.ASSET_ACCOUNT

                    for type in Account.AccountType:
                        if type.label == ACCOUNT_REGEX.match(account)[1]:
                            account_type = type

                    account_object = Account.objects.get(name=account_name, type=account_type)
                    value = float(account_object.balance_until_date(date - timedelta(days=1)).amount) - float(account_object.virtual_balance)
                else:
                    value = account_data["data"][date_index - 1]

                if date in date_entries.keys():
                    value += float(date_entries[date].amount)

                account_data["data"].append(round(value, 2))
            data["data"]["datasets"].append(account_data)

        return data


class TransactionChart(Chart):
    def __init__(self, data, user=None, income=False, expenses_budget=False, expenses_category=False, *args, **kwargs):
        self.income = income
        self.expenses_budget = expenses_budget
        self.expenses_category = expenses_category
        self.user = user
        self.currency = get_default_currency(user=self.user)

        super().__init__(data=data, *args, **kwargs)

    def _generate_chart_options(self):
        options = self._get_default_options()

        options["scales"] = {}
        options["legend"] = {"position": "right"}

        return options

    def _generate_chart_data(self):
        data = {"type": "pie", "data": {"labels": [], "datasets": [{"data": [], "borderWidth": [], "backgroundColor": [], "borderColor": []}]}}

        amounts = {}
        account_names = {}

        if self.income:
            self.data = [item for item in self.data if not item.amount.amount < 0]
        else:
            self.data = [item for item in self.data if item.amount.amount < 0]

        for transaction in self.data:
            account_name = "External account (untracked)"

            if self.income:
                if transaction.journal.type == TransactionJournal.TransactionType.START:
                    account_name = "Starting balance"

                else:
                    if len(transaction.journal.source_accounts) > 0:
                        account_name = ", ".join([account["account"] for account in transaction.journal.source_accounts])

            else:
                if self.expenses_budget and transaction.journal.budget is not None:
                    account_name = transaction.journal.budget.budget.name

                if self.expenses_category and transaction.journal.category is not None:
                    account_name = transaction.journal.category.name

            amount = amounts.get(account_name, 0.0)
            amount += float(transaction.amount.amount)
            amounts[account_name] = amount

        if not self.income:
            amounts.pop("External account (untracked)", None)

        counter = 1
        for account, amount in amounts.items():
            color = get_color_code(counter)
            counter += 1

            data["data"]["labels"].append("{account} ({currency})".format(account=account, currency=get_currency(self.currency, self.user)))
            data["data"]["datasets"][0]["data"].append(round(amount, 2))
            data["data"]["datasets"][0]["borderWidth"].append(2)
            data["data"]["datasets"][0]["backgroundColor"].append("rgba({color}, 1.0)".format(color=color))
            data["data"]["datasets"][0]["borderColor"].append("rgba(255, 255, 255, 1.0)".format(color=color))

        return data
from django.db.models import Sum

from djmoney.money import Money
from datetime import timedelta

import json
import re

from .models import get_default_currency, TransactionJournal, Transaction, Account
from .utilities import get_currency


def get_color_code(i):
    color_codes = ["98, 181, 229", "134, 188, 37", "152, 38, 73", "124, 132, 131", "213, 216, 135", "247, 174, 248"]
    color = (i - 1) % len(color_codes)

    return color_codes[color]


class Chart:
    def __init__(self, data):
        self.data = data

    def generate_json(self):
        chart_data = self._generate_chart_data()
        chart_options = self._generate_chart_options()
        chart_data["options"] = chart_options

        return json.dumps(chart_data).replace('"<<', "").replace('>>"', "")

    def _generate_chart_data(self):
        raise NotImplementedError

    def _generate_chart_options(self):
        return self._get_default_options()

    def _get_default_options(self):
        return {
            "maintainAspectRatio": False,
            "legend": {
                "display": False,
            },
            "animation": {"duration": 0},
            "responsive": True,
            "tooltips": {
                "backgroundColor": "#f5f5f5",
                "titleFontColor": "#333",
                "bodyFontColor": "#666",
                "bodySpacing": 4,
                "xPadding": 12,
                "mode": "nearest",
                "intersect": 0,
                "position": "nearest",
            },
            "scales": {
                "yAxes": [
                    {
                        "barPercentage": 1.6,
                        "gridLines": {"drawBorder": False, "color": "rgba(29,140,248,0.0)", "zeroLineColor": "transparent"},
                        "ticks": {"padding": 20, "fontColor": "#9a9a9a"},
                    }
                ],
                "xAxes": [
                    {
                        "type": "time",
                        "time": {"unit": "day"},
                        "barPercentage": 1.6,
                        "gridLines": {"drawBorder": False, "color": "rgba(225,78,202,0.1)", "zeroLineColor": "transparent"},
                        "ticks": {"padding": 20, "fontColor": "#9a9a9a"},
                    }
                ],
            },
        }


class AccountChart(Chart):
    def __init__(self, data, accounts, start_date, end_date, user=None, *args, **kwargs):
        self.start_date = start_date
        self.end_date = end_date
        self.accounts = accounts
        self.user = user
        self.currency = get_default_currency(user=self.user)

        super().__init__(data=data, *args, **kwargs)

    def _generate_chart_options(self):
        options = self._get_default_options()

        options["tooltips"]["callbacks"] = {
            "label": "<<function(tooltipItems, data) { return data.datasets[tooltipItems.datasetIndex].label + ': ' + tooltipItems.yLabel + ' (%s)'; }>>"
            % get_currency(self.currency, self.user)
        }

        if abs((self.end_date - self.start_date).days) > 150:
            options["scales"]["xAxes"][0]["time"]["unit"] = "week"

        return options

    def _generate_chart_data(self):
        dates = []

        for days_to_add in range(abs((self.end_date - self.start_date).days) + 1):
            day = self.start_date + timedelta(days=days_to_add)
            dates.append(day)

        data = {"type": "line", "data": {"labels": [date.strftime("%d %b %Y") for date in dates], "datasets": []}}

        accounts = {}
        accounts_virtual_balance = {}

        for item in self.data:
            if str(item.amount.currency) == str(self.currency) and item.account is not None:
                account_key = "{type} - {account}".format(type=item.account.get_type_display(), account=item.account.name)

                if account_key in accounts.keys():
                    date_entry = accounts[account_key].get(item.journal.date, Money(0, self.currency))
                    date_entry += item.amount

                    accounts[account_key][item.journal.date] = date_entry

                else:
                    accounts[account_key] = {item.journal.date: item.amount}
                    accounts_virtual_balance[account_key] = item.account.virtual_balance

        for account in self.accounts:
            account_key = "{type} - {account}".format(type=account.get_type_display(), account=account.name)

            if account_key not in accounts.keys():
                accounts[account_key] = {}

        counter = 1
        for account, date_entries in accounts.items():
            color = get_color_code(counter)

            account_data = {
                "label": account,
                "fill": "!1",
                "borderColor": "rgba({color}, 1.0)".format(color=color),
                "borderWidth": 2,
                "borderDash": [],
                "borderDash0ffset": 0,
                "pointBackgroundColor": "rgba({color}, 1.0)".format(color=color),
                "pointBorderColor": "rgba(255,255,255,0)",
                "pointHoverBackgroundColor": "rgba({color}, 1.0)".format(color=color),
                "pointBorderWidth": 20,
                "pointHoverRadius": 4,
                "pointHoverBorderWidth": 15,
                "pointRadius": 4,
                "data": [],
            }

            if abs((self.end_date - self.start_date).days) > 150:
                account_data["pointRadius"] = 0

            counter += 1

            for date_index in range(len(dates)):
                date = dates[date_index]

                value = 0

                if date_index == 0:
                    ACCOUNT_REGEX = re.compile(r"(.*)\s-\s(.*)")

                    account_name = ACCOUNT_REGEX.match(account)[2]
                    account_type = Account.AccountType.ASSET_ACCOUNT

                    for type in Account.AccountType:
                        if type.label == ACCOUNT_REGEX.match(account)[1]:
                            account_type = type

                    account_object = Account.objects.get(name=account_name, type=account_type)
                    value = float(account_object.balance_until_date(date - timedelta(days=1)).amount) - float(account_object.virtual_balance)
                else:
                    value = account_data["data"][date_index - 1]

                if date in date_entries.keys():
                    value += float(date_entries[date].amount)

                account_data["data"].append(round(value, 2))
            data["data"]["datasets"].append(account_data)

        return data


class TransactionChart(Chart):
    def __init__(self, data, user=None, income=False, expenses_budget=False, expenses_category=False, *args, **kwargs):
        self.income = income
        self.expenses_budget = expenses_budget
        self.expenses_category = expenses_category
        self.user = user
        self.currency = get_default_currency(user=self.user)

        super().__init__(data=data, *args, **kwargs)

    def _generate_chart_options(self):
        options = self._get_default_options()

        options["scales"] = {}
        options["legend"] = {"position": "right"}

        return options

    def _generate_chart_data(self):
        data = {"type": "pie", "data": {"labels": [], "datasets": [{"data": [], "borderWidth": [], "backgroundColor": [], "borderColor": []}]}}

        amounts = {}
        account_names = {}

        if self.income:
            self.data = [item for item in self.data if not item.amount.amount < 0]
        else:
            self.data = [item for item in self.data if item.amount.amount < 0]

        for transaction in self.data:
            account_name = "External account (untracked)"

            if self.income:
                if transaction.journal.type == TransactionJournal.TransactionType.START:
                    account_name = "Starting balance"

                else:
                    if len(transaction.journal.source_accounts) > 0:
                        account_name = ", ".join([account["account"] for account in transaction.journal.source_accounts])

            else:
                if self.expenses_budget and transaction.journal.budget is not None:
                    account_name = transaction.journal.budget.budget.name

                if self.expenses_category and transaction.journal.category is not None:
                    account_name = transaction.journal.category.name

            amount = amounts.get(account_name, 0.0)
            amount += float(transaction.amount.amount)
            amounts[account_name] = amount

        if not self.income:
            amounts.pop("External account (untracked)", None)

        counter = 1
        for account, amount in amounts.items():
            color = get_color_code(counter)
            counter += 1

            data["data"]["labels"].append("{account} ({currency})".format(account=account, currency=get_currency(self.currency, self.user)))
            data["data"]["datasets"][0]["data"].append(round(amount, 2))
            data["data"]["datasets"][0]["borderWidth"].append(2)
            data["data"]["datasets"][0]["backgroundColor"].append("rgba({color}, 1.0)".format(color=color))
            data["data"]["datasets"][0]["borderColor"].append("rgba(255, 255, 255, 1.0)".format(color=color))

        return data