import os
import pprint
import re
import string

import pkg_resources
import yaml
from bs4 import BeautifulSoup
from bs4.element import NavigableString, Tag
from django.apps.config import AppConfig

from crispy_layout_mixin import heads_and_tails
from legacy.migration import appconfs, get_renames
from legacy.scripts.utils import getsourcelines
from sapl.utils import listify

# to prevent removal by automatic organize imports on this file
assert appconfs

field_renames, model_renames = get_renames()


def _read_line(tr):
    for td in tr.find_all('td'):
        label = td.text.strip().split('\n')[0].strip(
            '\xa0' + string.whitespace)
        if label.endswith('(*)'):
            label = label[:-3].strip()
        names = [c.attrs['name']
                 for c in td.findAll()
                 if isinstance(c, Tag) and 'name' in c.attrs]
        if names:
            name = names[0].split('_', 1)[-1]
            yield name, label


def extract_title_and_fieldsets(model):
    filename = os.path.join(os.path.dirname(__file__),
                            'original_forms/%s.html' % model.__name__)
    try:
        with open(filename, 'r') as file:
            html_doc = file.read()
    except IOError:
        return None, []

    soup = BeautifulSoup(html_doc, 'html.parser')
    forms = soup.find_all('form')
    [form] = [f for f in forms if ('method', 'post') in f.attrs.items()]
    # children are either tags or strings...
    assert set(type(c) for c in form.children) == {Tag, NavigableString}
    # ... and all strings are empty
    assert all(not c.strip()
               for c in form.children if isinstance(c, NavigableString))

    title = soup.find('h1', {'class': 'firstHeading'})
    title = title.text.strip() if title else None
    fieldsets = [dict(
        legend=fieldset.find('legend').text if fieldset.find('legend') else '',
        lines=[list(_read_line(tr)) for tr in fieldset.find_all('tr')])
        for fieldset in form.find_all('fieldset')]

    return title, fieldsets


def get_names_labels(fieldsets):
    for fieldset in fieldsets:
        for line in fieldset['lines']:
            for name, label in line:
                yield name, label


def print_title_and_fieldsets(model):
    title, fieldsets = extract_title_and_fieldsets(model)
    print('#### %s ####\n' % title)
    for fieldset in fieldsets:
        print(fieldset['legend'])
        for line in fieldset['lines']:
            print('  ' + ' | '.join('%s : %s' % (id, label)
                                    for id, label in line))


def extract_verbose_names(model):
    title, fieldsets = extract_title_and_fieldsets(model)
    names_to_labels = dict(get_names_labels(fieldsets))

    field_names = [f.name for f in model._meta.fields if f.name != 'id']

    labels = {}
    field_names_to_old = field_renames[model]
    for name in field_names:
        old_name = field_names_to_old[name]
        label = names_to_labels.get(old_name, None)
        if label:
            labels[name] = label
            del names_to_labels[old_name]
    for name, label in labels.items():
        field_names.remove(name)
    non_matched = field_names, names_to_labels
    return title, labels, non_matched


@listify
def source_with_verbose_names(model):
    source = getsourcelines(model)
    title, labels, non_matched = extract_verbose_names(model)

    field_regex = ' *(.+) = (models\.[^\(]*)\((.*verbose_name=_\(.*\)|.*)\)'
    new_lines = []
    class_meta_already_exists = False
    for line in source[1:]:
        for regex, split in [
                (field_regex + ' *# (.+)', lambda groups: groups),
                (field_regex, lambda groups: groups + ('',))]:
            match = re.match(regex, line)
            if match:
                name, path, args, legacy_name = split(match.groups())
                if name in labels and 'verbose_name' not in args:
                    args = [args] if args.strip() else []
                    args.append("verbose_name=_('%s')" % labels[name])
                    args = ', '.join(args)
                new_lines.append(
                    ('    %s = %s(%s)' % (name, path, args), legacy_name))
                break
        else:
            if 'class Meta:' in line:
                class_meta_already_exists = True
            new_lines.append((line, ''))
    yield source[0].rstrip()
    cols = max(map(len, [line for line, _ in new_lines]))
    for line, legacy_name in new_lines:
        line = line.rstrip().ljust(cols)
        if legacy_name:
            yield line + '  # ' + legacy_name
        else:
            yield line

    # class Meta
    if class_meta_already_exists:
        return

    if title == 'Tabelas Auxiliares':
        title = ''
    title = title if title else ''

    def add_s(name):
        return ' '.join(
            p if p.endswith('s') else p + 's' for p in name.split())

    def remove_s(name):
        return ' '.join(p[:-1] if p.endswith('s') else p for p in name.split())

    if not title:
        # default title from model name
        title_singular = ' '.join(re.findall('[A-Z][^A-Z]*', model.__name__))
        title_singular = re.sub('cao\\b', 'ção', title_singular)
        title_singular = re.sub('ao\\b', 'ão', title_singular)
        title_plural = add_s(
            title_singular.replace('ção', 'ções').replace('ão', 'ões'))

    elif title.endswith('s'):
        title_singular = remove_s(
            title.replace('ções', 'ção').replace('ões', 'ão'))
        title_plural = title
    else:
        title_singular = title
        title_plural = add_s(title.replace('ção', 'ções').replace('ão', 'ões'))

    yield """
    class Meta:
        verbose_name = _('%s')
        verbose_name_plural = _('%s')""" % (title_singular, title_plural)


def print_app_with_verbose_names(app):
    print('##################################################################')
    header = '# -*- coding: utf-8 -*-\n'
    for line in getsourcelines(app.models_module):
        if line in ['# -*- coding: utf-8 -*-',
                    'from django.utils.translation import ugettext as _', ]:
            continue
        elif line == 'from django.db import models':
            header += '''from django.db import models
from django.utils.translation import ugettext_lazy as _
'''
        elif 'class' in line:
            break
        else:
            header += line + '\n'
    print(header.strip())
    for model in app.models.values():
        print('\n')
        for p in source_with_verbose_names(model):
            print(p)


def list_models_with_no_scrapped_data(app):
    for model in app.models.values():
        if not any(extract_verbose_names(model)[:2]):
            print(model.__name__)


@listify
def colsplit(names):
    n = len(names)
    d, r = 12 // n, 12 % n
    spans = [d + 1] * r + [d] * (n - r)
    return zip(names, spans)


def model_name_as_snake(model):
    return re.sub('([A-Z]+)', r'_\1', model.__name__).lower().strip('_')


old_names_adjustments = yaml.load(pkg_resources.resource_string(
    __name__, 'old_names_adjustments.yaml'))


@listify
def extract_fieldsets_for_current(model):
    __, fieldsets = extract_title_and_fieldsets(model)
    if not fieldsets:
        return

    try:
        reverse_field_renames = {v: k for k, v in field_renames[model].items()}
        adjustments = old_names_adjustments.get(model.__name__)
        if adjustments:
            reverse_field_renames.update(adjustments)

        for fieldset in fieldsets:
            rows = [
                colsplit(
                    [reverse_field_renames.get(name, '%s_FIXME' % name)
                     for name, ___ in line])
                for line in fieldset['lines'] if line
            ]
            yield [fieldset['legend']] + rows
    except Exception as e:
        print_title_and_fieldsets(model)
        raise Exception(e, model)


class Under(object):

    def __init__(self, arg):
        self.arg = arg

    def __repr__(self):
        return "_('%s')" % self.arg


GAP = 12
pretty_printer = pprint.PrettyPrinter(width=80 - GAP)


def print_crispy_form(model_or_app):
    if isinstance(model_or_app, AppConfig):
        for model in model_or_app.models.values():
            print_crispy_form(model)
    else:
        model = model_or_app

    fieldsets = extract_fieldsets_for_current(model)
    if fieldsets:
        print("""
class %(name)sForm(forms.ModelForm):

    class Meta:
        model = %(name)s
        exclude = []

    def __init__(self, *args, **kwargs):
        super(%(name)sForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = SaplFormLayout(
""" % {'name': model.__name__})

        for legend, rows in heads_and_tails(fieldsets):
            lines = pretty_printer.pformat([Under(legend)] + rows) + ',\n\n'
            for line in lines.splitlines():
                print(' ' * GAP + line if line.strip() else '')

        print("        )")