# -*- coding: utf-8 -*-

import locale
import itertools
import csv
import re

from datetime import datetime

from contextlib import contextmanager
from ofxstatement.parser import StatementParser
from ofxstatement.plugin import Plugin
from ofxstatement.statement import Statement, StatementLine, generate_transaction_id


def take(iterable, n):
    """Return first n items of the iterable as a list."""
    return list(itertools.islice(iterable, n))


def drop(iterable, n):
    """Drop first n items of the iterable and return result as a list."""
    return list(itertools.islice(iterable, n, None))


def head(iterable):
    """Return first element of the iterable."""
    return take(iterable, 1)[0]


@contextmanager
def scoped_setlocale(category, loc=None):
    """Scoped version of locale.setlocale()"""
    orig = locale.getlocale(category)
    try:
        yield locale.setlocale(category, loc)
    finally:
        locale.setlocale(category, orig)


def atof(string, loc=None):
    """Locale aware atof function for our parser."""
    with scoped_setlocale(locale.LC_NUMERIC, loc):
        return locale.atof(string)


#
# Yeah, maybe CsvStatementParser is the right way to do it but we have some custom logic here
# that doesn't fit it nicely.
#
class AlfaBankStatementParser(StatementParser):
    bank_id = 'AlfaBank'
    date_format = '%d.%m.%y'

    def __init__(self, fin, encoding=None, locale=None, brief=False):
        self.locale = locale
        self.encoding = encoding
        self.brief = brief

        with open(fin, 'r', encoding=self.encoding) as f:
            self.lines = f.readlines()

        self.validate()
        self.statement = self.parse_statement()

    @property
    def reader(self):
        return csv.reader(self.lines, delimiter=';')

    @property
    def header(self):
        return head(self.reader)

    @property
    def rows(self):
        return drop(self.reader, 1)

    def validate(self):
        """
        Validate to ensure csv has the same header we expect.
        """

        expected = [
            u"Тип счёта",
            u"Номер счета",
            u"Валюта",
            u"Дата операции",
            u"Референс проводки",
            u"Описание операции",
            u"Приход",
            u"Расход",
            u""
        ]
        actual = self.header
        if expected != actual:
            msg = "\n".join([
                "Header template doesn't match:",
                "expected: %s" % expected,
                "actual  : %s" % actual
            ])
            raise ValueError(msg)

    def parse_statement(self):
        """
        Parse statement object.
        """

        account_ids = set()
        for row in self.rows:
            account_id = row[1]
            account_ids.add(account_id)
        if len(account_ids) < 1:
            raise ValueError("No accounts found: %s" % account_ids)
        if len(account_ids) > 1:
            raise ValueError("More than one account found: %s" % account_ids)

        currency_ids = set()
        for row in self.rows:
            currency_id = row[2]
            currency_ids.add(currency_id)
        if len(currency_ids) < 1:
            raise ValueError("No currency found: %s" % currency_ids)
        if len(currency_ids) > 1:
            raise ValueError("More than one currency found: %s" % currency_ids)

        return Statement(
            bank_id=self.bank_id,
            account_id=head(account_ids),
            currency=head(currency_ids),
        )

    def split_records(self):
        for row in self.rows:
            yield row

    def parse_record(self, row):
        account_name, account_id, currency, date, refnum, description, debit, credit = take(row, 8)

        stmt_line = StatementLine()
        stmt_line.date = datetime.strptime(date, self.date_format)
        stmt_line.memo = description
        stmt_line.refnum = refnum

        debit = atof(debit, self.locale)
        credit = atof(credit, self.locale)

        assert (debit > 0) ^ (credit > 0)
        if debit > 0:
            stmt_line.amount = debit
        elif credit > 0:
            stmt_line.amount = -1 * credit
        else:
            stmt_line.amount = 0.0

        #
        # Looks like AlfaBank formats description for card transactions so it includes the actual purchase date
        # within e.g.
        #
        # 123456++++++7890    12312312\111\NOVOSIBIRSK\CHITAI GOROD             10.01.16 08.01.16       617.00  RUR
        #
        # we cleanup it (optionally) by leaving only the actual description part i.e.
        #
        # 12312312\111\NOVOSIBIRSK\CHITAI GOROD
        #
        if refnum.startswith("CRD"):
            m = re.match('([0-9+]+) +(.+) +([0-9]{2}\.[0-9]{2}\.[0-9]{2}) ([0-9]{2}\.[0-9]{2}\.[0-9]{2}) +([0-9]+\.[0-9]+) +([^ ]+)', stmt_line.memo)
            if m:
                card_id, card_memo, _, card_date, card_amount, card_currency = m.groups()
                if self.brief:
                    stmt_line.memo = card_memo
                stmt_line.date_user = datetime.strptime(card_date, self.date_format)

        stmt_line.id = generate_transaction_id(stmt_line)
        return stmt_line


def parse_bool(value):
    if value in ('True', 'true', '1'):
        return True
    if value in ('False', 'false', '0'):
        return False
    raise ValueError("Can't parse boolean value: %s" % value)


class AlfaBankPlugin(Plugin):
    def get_parser(self, fin):
        kwargs = {
            'locale': 'ru_RU',
            'encoding': 'cp1251',
        }
        if self.settings:
            if 'brief' in self.settings:
                kwargs['brief'] = parse_bool(self.settings.get('brief'))
            if 'encoding' in self.settings:
                kwargs['encoding'] = self.settings.get('encoding')
            if 'locale' in self.settings:
                kwargs['locale'] = self.settings.get('locale')
        return AlfaBankStatementParser(fin, **kwargs)
