# -*- coding: UTF-8 -*-
# Copyright 2009-2013 Rumma & Ko Ltd
# This file is part of the Lino project.
# Lino is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# Lino is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with Lino ; if not, see <http://www.gnu.org/licenses/>.
"""

.. management_command:: initdb_tim

Performs a database reset and initial import of your TIM data. 
Mandatory argument is the path to your TIM data directory.

"""

from builtins import str
import os
import sys
import datetime

from dateutil import parser as dateparser

from django.conf import settings
from django.core.management import call_command
from django.db import IntegrityError
from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand, CommandError

#~ from lino import lino_site
from lino.utils import dbfreader
from lino.utils import dblogger
#~ from lino import diag

from lino_xl.lib.contacts.utils import name2kw, street2kw
from lino.utils import join_words
#~ from lino_xl.lib.contacts.models import name2kw, street2kw, join_words
from lino.utils.instantiator import Instantiator

from lino.core.utils import resolve_model, obj2str
from lino.core.utils import is_valid_email
import lino

from lino.utils import confirm, iif
from lino.core.utils import app_labels
from lino_xl.lib.beid.mixins import BeIdCardTypes

Activity = resolve_model('pcsw.Activity')
Country = resolve_model('countries.Country')
Place = resolve_model('countries.Place')
Person = resolve_model('contacts.Person')
Company = resolve_model('contacts.Company')


def store(kw, **d):
    for k, v in d.items():
        if v is not None:
            # see :doc:`/blog/2011/0711`
            #~ if v:
            kw[k] = v


#~ def convert_username(name):
#~ return name.lower()


def convert_sex(v):
    if v in ('W', 'F'):
        return 'F'
    if v == 'M':
        return 'M'
    return None


def isolang(x):
    if x == 'K':
        return 'et'
    if x == 'E':
        return 'en'
    if x == 'D':
        return 'de'
    if x == 'F':
        return 'fr'
    if x == 'N':
        return 'nl'


def is_company(data):
    # wer eine nationalregisternummer hat ist eine Person, selbst wenn er auch
    # eine MWst-Nummer hat.
    if data.get('NB2', False):
        return False
    if data.get('NOTVA', False):
        #~ if data.get('NOTVA',False):
        return True
    return False


def store_date(row, obj, rowattr, objattr):
    v = row[rowattr]
    if v:
        if isinstance(v, basestring):
            v = dateparser.parse(v)
        setattr(obj, objattr, v)


def ADR_id(cIdAdr):
    assert len(cIdAdr) == 3
    #~ assert [cIdAdr:-3] == '000'
    try:
        return 199000 + int(cIdAdr)
    except ValueError as e:
        return None


def country2kw(row, kw):
    # for both PAR and ADR

    if 'PROF' in row:
        activity = row['PROF']
        if activity:
            try:
                activity = int(activity)
            except ValueError:
                dblogger.debug("Ignored invalid value PROF = %r", activity)
            else:
                if activity:
                    try:
                        activity = Activity.objects.get(pk=activity)
                    except Activity.DoesNotExist:
                        activity = Activity(id=activity, name=str(activity))
                        activity.save(force_insert=True)
                    kw.update(activity=activity)

    country = row['PAYS']
    if country:
        try:
            country = Country.objects.get(short_code__exact=country)
        except Country.DoesNotExist:
            country = Country(isocode=country,
                              name=country,
                              short_code=country)
            country.save()
        kw.update(country=country)

    email = row['EMAIL']
    if email and is_valid_email(email):
        kw.update(email=email)
    store(
        kw,
        phone=row['TEL'],
        fax=row['FAX'],
    )

    kw.update(street2kw(join_words(row['RUE'], row['RUENUM'], row['RUEBTE'])))

    zip_code = row['CP']
    if zip_code:
        kw.update(zip_code=zip_code)
        try:
            city = Place.objects.get(
                country=country,
                zip_code__exact=zip_code,
            )
            kw.update(city=city)
        except Place.DoesNotExist as e:
            city = Place(zip_code=zip_code, name=zip_code, country=country)
            city.save()
            kw.update(city=city)
            #~ dblogger.warning("%s-%s : %s",row['PAYS'],row['CP'],e)
        except Place.MultipleObjectsReturned as e:
            dblogger.warning("%s-%s : %s", row['PAYS'], row['CP'], e)


def par2person(row, person):
    #~ person.is_active = iif(row['IDPRT']=='I',False,True)
    if row['IDPRT'] == 'S':
        person.is_cpas = True
    elif row['IDPRT'] == 'A':
        person.is_senior = True


def pxs2person(row, person):

    kw = {}
    store(
        kw,
        card_number=row['CARDNUMBER'],
        card_issuer=row.get('CARDISSUER', ''),  # 20110110
        noble_condition=row.get('NOBLEECOND', ''),  # 20110110
        birth_place=row.get('BIRTHPLACE', ''),
        remarks2=row.get('MEMO', ''),
        gender=convert_sex(row['SEXE']))
    for k, v in kw.items():
        setattr(person, k, v)

    par2person(row, person)

    if 'CARDTYPE' in row:
        #~ row.card_type = BeIdCardTypes.items_dict.get(row['CARDTYPE'].strip(),'')
        from lino_welfare.modlib.pcsw import models as pcsw
        if row['CARDTYPE'] == 0:
            person.card_type = BeIdCardTypes.blank_item
        else:
            person.card_type = BeIdCardTypes.get_by_value(str(row['CARDTYPE']))
    if row['IDMUT']:
        try:
            person.health_insurance = Company.objects.get(
                pk=ADR_id(row['IDMUT']))
        except ValueError as e:
            dblogger.warning(u"%s : invalid health_insurance %r",
                             obj2str(person), row['IDMUT'])
        except Company.DoesNotExist as e:
            dblogger.warning(u"%s : health_insurance %s not found",
                             obj2str(person), row['IDMUT'])

    if row['APOTHEKE']:
        try:
            person.pharmacy = Company.objects.get(pk=int(row['APOTHEKE']))
        except ValueError:
            dblogger.warning(u"%s : invalid pharmacy %r", obj2str(person),
                             row['APOTHEKE'])
        except Company.DoesNotExist:
            dblogger.warning(u"%s : pharmacy %s not found", obj2str(person),
                             row['APOTHEKE'])

    nat = row['NATIONALIT']
    if nat:
        try:
            country = Country.objects.get(short_code__exact=nat)
        except Country.DoesNotExist:
            country = Country(isocode=nat, name=nat, short_code=nat)
            country.save()
        person.nationality = country

    store_date(row, person, 'GEBDAT', 'birth_date')
    store_date(row, person, 'VALID1', 'card_valid_from')
    store_date(row, person, 'VALID2', 'card_valid_until')


def try_full_clean(i):
    while True:
        try:
            i.full_clean()
        except ValidationError as e:
            if not hasattr(e, 'message_dict'):
                raise
            for k in e.message_dict.keys():
                fld = i._meta.get_field(k)
                v = getattr(i, k)
                setattr(i, k, fld.default)
                dblogger.warning("%s : ignoring value %r for %s : %s",
                                 obj2str(i), v, k, e)
        return


def load_dbf(dbpath, tableName, load):
    fn = os.path.join(dbpath, '%s.DBF' % tableName)
    f = dbfreader.DBFFile(fn, codepage="cp850")
    dblogger.info("Loading %d records from %s...", len(f), fn)
    f.open()
    for dbfrow in f:
        i = load(dbfrow)
        if i is not None:
            i = settings.TIM2LINO_LOCAL(tableName, i)
            if i is not None:

                try_full_clean(i)

                try:
                    i.save()
                    #~ dblogger.debug("%s has been saved",i)
                except Exception as e:
                    #~ except IntegrityError,e:
                    dblogger.warning("Failed to save %s from %s : %s",
                                     obj2str(i), dbfrow, e)
                    dblogger.exception(e)
    f.close()


def load_tim_data(dbpath):

    #~ from django.contrib.auth import models as auth
    from lino.modlib.users import models as auth

    def load(row):
        #~ users.User.objects.create_user(row['USERID'],row['EMAIL'] or '','')
        if not row['EMAIL']:
            return
        username = settings.TIM2LINO_USERNAME(row['USERID'])
        if username is None:
            return
        d = name2kw(row['NAME'], False)
        now = datetime.datetime.now()
        user = users.User(
            username=username,
            #~ username=row['USERID'].lower(),
            email=row['EMAIL'],
            first_name=d['first_name'],
            last_name=d['last_name'])
        user.set_password('temp')
        return user

    load_dbf(dbpath, 'USR', load)

    def load(row):
        kw = dict(
            id=row['IDPRF'],
            name=row['LIBELL'] or '',
            lst104=(row['LST104'] in ('X', 'x')),
        )
        return Activity(**kw)

    load_dbf(dbpath, 'PRF', load)

    # Countries already exist after initial_data, but their short_code is
    # needed as lookup field for Places.
    def load(row):
        if not row['ISOCODE']:
            return
        try:
            country = Country.objects.get(isocode__exact=row['ISOCODE'])
        except Country.DoesNotExist as e:
            country = Country(isocode=row['ISOCODE'])
            country.name = row['NAME']
        if row['IDNAT']:
            country.short_code = row['IDNAT']
        return country
        #~ kw = dict(short_code=row['IDNAT'],name=row['NAME'],isocode=row['ISOCODE'])
        #~ return Country(**kw)

    load_dbf(dbpath, 'NAT', load)

    def load(row):
        try:
            country = Country.objects.get(short_code__exact=row['PAYS'])
        except Country.DoesNotExist as e:
            return
        kw = dict(
            zip_code=row['CP'] or '',
            name=row['NOM'] or row['CP'],
            country=country,
        )
        return Place(**kw)

    load_dbf(dbpath, 'PLZ', load)

    def load(row):
        if row['TYPE'] == 'MUT':
            pk = ADR_id(row['IDMUT'])
            if pk:
                d = dict(id=pk)
                d.update(name=row['NAME'])
                #~ 20101230 store(d,gsm=row['GSM'])
                country2kw(row, d)
                return Company(**d)

    load_dbf(dbpath, 'ADR', load)

    def load(row):
        kw = {}
        #~ kw.update(street2kw(join_words(row['RUE'],row['RUENUM'],row['RUEBTE'])))
        store(kw, id=int(row['IDPAR']))

        if is_company(row):
            cl = Company
            store(
                kw,
                vat_id=row['NOTVA'],
                prefix=row['ALLO'],
                name=row['FIRME'],
            )
        else:
            cl = Person
            if row['IDPRT'] == 'S':
                kw.update(is_cpas=True)
            elif row['IDPRT'] == 'A':
                kw.update(is_senior=True)
            elif row['IDPRT'] == 'I':
                kw.update(is_active=False)
            kw.update(**name2kw(row['FIRME']))
            store(
                kw,
                national_id=row['NB2'],
                gesdos_id=row['NB1'],
                bank_account1=row['COMPTE1'],
                bank_account2=row['COMPTE2'],
                title=row['ALLO'],
            )
            if row['IDUSR']:
                username = settings.TIM2LINO_USERNAME(row['IDUSR'])
                if username is not None:
                    try:
                        kw.update(coach1=users.User.objects.get(
                            username=username))
                        #~ kw.update(user=users.User.objects.get(username=username))
                    except users.User.DoesNotExist as e:
                        dblogger.warning(
                            "PAR:%s PAR->IdUsr %r (converted to %r) doesn't exist!",
                            row['IDPAR'], row['IDUSR'], username)
        language = isolang(row['LANGUE'])
        #~ if language:
        #~ language = Language.objects.get(pk=language)
        store(
            kw,
            language=language,
            remarks=row['MEMO'],
        )

        country2kw(row, kw)

        return cl(**kw)

    load_dbf(dbpath, 'PAR', load)

    def load(row):
        #~ if row['IDPRT'] != 'S':
        #~ return
        try:
            person = Person.objects.get(pk=int(row['IDPAR']))
        except Person.DoesNotExist as e:
            return
        pxs2person(row, person)

        return person

    load_dbf(dbpath, 'PXS', load)


class Command(BaseCommand):
    args = '<path_to_tim_data_dir>'
    help = 'Performs a database reset and initial import of your TIM data'

    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError(
                'Please specify the path to your TIM data directory')

        dbname = settings.DATABASES['default']['NAME']
        if not confirm(
                "Gonna reset your database (%s).\nAre you sure (y/n) ?" %
                dbname):
            raise CommandError("User abort.")

        options.update(interactive=False)

        dblogger.info("Lino initdb_tim started on database %s." % dbname)
        dblogger.info(lino.welcome_text())

        apps = app_labels()
        call_command('reset', *apps, **options)

        #~ call_command('reset',
        #~ 'contacts','pcsw','countries','auth','notes',
        #~ 'countries','links','uploads',
        #~ interactive=False)

        call_command('syncdb', **options)

        #~ NoteType = resolve_model('notes.NoteType')
        #~ n = NoteType.objects.all().count()
        #~ assert n == 21, 'NoteType.objects.all().count() == %d (expected 21)' % n

        #~ lino_site.initdb()
        #~ for o in tim_fixture_objects():
        #~ o.save()
        #~ logger.info('lino_site.fill() done. Starting load_tim_data()...')
        load_tim_data(args[0])

        dblogger.info("Lino initdb_tim done on database %s." % dbname)
