"""Members modelsmain page."""
from django.db import models
from outils.common_imports import *
from outils.images_imports import *

from outils.common import OdooAPI
from outils.common import CouchDB
from products.models import OFF
from envelops.models import CagetteEnvelops

import sys
import pytz
import locale
import re
import dateutil.parser
from datetime import date



FUNDRAISING_CAT_ID = {'A': 1, 'B': 2, 'C': 3}

class CagetteMember(models.Model):
    """Class to handle cagette Odoo member."""
    m_default_fields = ['name', 'parent_name', 'sex', 'image_medium', 'active',
                        'barcode_base', 'barcode', 'shift_type',
                        'is_associated_people', 'is_member', 'shift_type',
                        'display_ftop_points', 'display_std_points',
                        'is_exempted', 'cooperative_state', 'date_alert_stop']

    m_short_default_fields = ['name', 'barcode_base']

    def __init__(self, id):
        """Init with odoo id."""
        self.id = int(id)
        self.o_api = OdooAPI()

    def update_from_ajax(self, request):
        result = {}
        try:
            fields = {}
            for key, value in request.POST.items():
                fields[key] = value
            result['update'] = self.o_api.update('res.partner', [self.id], fields)
        except Exception as e:
            result['error'] = str(e)
        return result

    def set_odoo_image(self, image):
        """Record base64 image associated to member."""
        api = OdooAPI()
        f = {'image': image}
        return api.update('res.partner', [self.id], f)

    def attach_message(self, body):
        params = {'message_type': 'comment', 'subtype': 'mail.mt_comment', 'body': body}
        return self.o_api.execute('res.partner', 'message_post', [self.id], params)

    def get_image(self):
        image = ''
        cond = [['id', '=', self.id]]
        fields = ['image_medium']
        res = self.o_api.search_read('res.partner', cond, fields)
        if res and len(res) == 1:
            image = res[0]['image_medium']
        return image

    def get_member_points(self, shift_type):
        points_field = 'final_standard_point' if shift_type == "standard" else 'final_ftop_point'

        cond = [['id', '=', self.id]]
        fields = ['id', points_field]
        res = self.o_api.search_read('res.partner', cond, fields)

        if res and len(res) == 1:
            return res[0][points_field]
        else:
            return None

    def update_member_points(self, data):
        """
            ex:
            data = {
                'name': reason,
                'shift_id': False,
                'type': stype,
                'partner_id': self.id,
                'point_qty': pts
            }
        """

        try:
            return self.o_api.create('shift.counter.event', data)
        except Exception as e:
            print(str(e))


# # # BDM
    def save_partner_info(self, partner_id, fieldsDatas):
        return self.o_api.update('res.partner', partner_id,  fieldsDatas)


    @staticmethod
    def retrieve_data_according_keys(keys, full=False):
        api = OdooAPI()
        cond = []
        for k in keys:
            cond.append([k, '=', keys[k]])
        if full is True:
            fields = ['image_medium', 'barcode_base', 'barcode', 'create_date',
                      'cooperative_state', 'name', 'birthdate', 'street', 'street2',
                      'zip', 'city', 'email', 'mobile', 'phone', 'total_partner_owned_share',
                      'amount_subscription', 'active_tmpl_reg_line_count', 'is_exempted',
                      'shift_type', 'current_template_name',
                      'final_standard_point', 'final_ftop_point',
                      'date_alert_stop','date_delay_stop', 'sex']
        else:
            fields = ['name', 'email', 'birthdate',
                      'sex', 'country_id', 'total_partner_owned_share',
                      'barcode_base', 'tmpl_reg_line_ids']
        return api.search_read('res.partner', cond, fields, 1, 0,
                                     'id DESC')

    @staticmethod
    def get_credentials(request, external=False, with_id=False):
        import hashlib

        data = {}

        login = request.POST.get('login')
        password = request.POST.get('password')
        fp = request.POST.get('fp') #  fingerprint (prevent using stolen cookies)
        if login and password:
            api = OdooAPI()
            login = login.strip()
            cond = [['email', '=', login]]
            if getattr(settings, 'ALLOW_NON_MEMBER_TO_CONNECT', False) is False:
                cond.append('|')
                cond.append(['is_member', '=', True])
                cond.append(['is_associated_people', '=', True])

            fields = ['name', 'email', 'birthdate', 'create_date', 'cooperative_state', 'is_associated_people', 'barcode_base']
            res = api.search_read('res.partner', cond, fields)
            if (res and len(res) >= 1):
                coop_id = None
                for item in res:
                    coop = item
                    if item["birthdate"] is not False:
                        coop_birthdate = item['birthdate']
                        coop_state = item['cooperative_state']
                    if item["is_associated_people"] == True:
                        coop_id = item['id']

                y, m, d = coop_birthdate.split('-')
                password = password.replace('/', '')
                if (password == d + m + y):
                    if coop_id is None:
                        coop_id = coop['id']
                    data['id'] = coop_id
                    auth_token_seed = fp + coop['create_date']
                    data['auth_token'] = hashlib.sha256(auth_token_seed.encode('utf-8')).hexdigest()
                    data['token'] = hashlib.sha256(coop['create_date'].encode('utf-8')).hexdigest()
                    data['coop_state'] = coop_state
                    if external is True:
                        from outils.functions import extract_firstname_lastname
                        name_sep = getattr(settings, 'SUBSCRIPTION_NAME_SEP', ' ')
                        name_elts = extract_firstname_lastname(coop['name'], name_sep)
                        data['lastname'] = name_elts['lastname']
                        if name_elts['firstname'] != name_elts['lastname']:
                            data['firstname'] = name_elts['firstname']
                        else:
                            data['firstname'] = ''
                        data['coop_num'] = coop['barcode_base']

                if not ('auth_token' in data):
                    data['failure'] = True
                    data['msg'] = "Erreur dans le mail ou le mot de passe"
                    data['errnum'] = 1
            else:
                data['failure'] = True
                data['msg'] = "Erreur dans le mail ou le mot de passe"
                data['errnum'] = 2
                #  data['res'] = res

        elif external is False and 'token' in request.COOKIES and 'id' in request.COOKIES:
            api = OdooAPI()
            cond = [['id', '=', request.COOKIES['id']]]
            fields = ['create_date','email']
            res = api.search_read('res.partner', cond, fields)
            if (res and len(res) == 1):
                login = res[0]['email']
                calc_token = hashlib.sha256(res[0]['create_date'].encode('utf-8')).hexdigest()
                if calc_token == request.COOKIES['token']:
                    data['success'] = True
                    if with_id is True:
                        data['id'] = res[0]['id']
                else:
                    data['failure'] = True
                    data['errnum'] = 3
        else:
            data['failure'] = True
        if not ('failure' in data):
            if external is False:
                data['login'] = login
                c_db_data = CagetteMember.get_couchdb_data(login)
                if len(c_db_data) > 0 and 'validation_state' in c_db_data:
                    data['validation_state'] = c_db_data['validation_state']
        #  print(str(data))
        return data

    @staticmethod
    def send_new_password_link(request):
        result = {}
        email = request.POST.get('email')
        if len(email) > 5 and '@' in email:
            api = OdooAPI()
            cond = [['email', '=', email]]
            fields = ['create_date']
            res = api.search_read('res.partner', cond, fields)
            if (res and len(res) == 1):

                result['msg'] = 'Trouvé ' + str(res[0]['create_date'])
        else:
            result['error'] = 'Email non valide'

        return result

    def get_data(self, full=False):
        """Get member data using Odoo API."""
        return CagetteMember.retrieve_data_according_keys({'id':self.id}, full)

    @staticmethod
    def standalone_create_envelops(request):
        res = {}
        fields = {'checks': []}
        try:
            checks = request.POST.getlist("checks[]")
            if len(checks) > 0:
                for c in checks:
                    fields['checks'].append(int(c))
            for key, value in request.POST.items():
                if key != "checks[]":
                    fields[key] = value
            res = CagetteEnvelops().create_or_update_envelops(fields)
        except Exception as e:
            res['error'] = str(e)
        return res

    def add_pts(self, stype, pts, reason):
        fields = {'name': reason,
                  'shift_id': False,
                  'type': stype,
                  'partner_id': self.id,
                  'point_qty': pts
                 }
        return self.o_api.create('shift.counter.event', fields)

    def add_first_point(self, stype):
        """To prevent members to have -1 point if service is too close to subscription"""
        ltype = 'standard'
        if (stype == 2):
            ltype = 'ftop'
        self.add_pts(ltype, 1, 'Point de bienvenue')

    def generate_barcode(self):
        return self.o_api.execute('res.partner', 'generate_barcode', [self.id])

    def generate_base_and_barcode(self, data=None):
        """Call Odoo methods to generate base and barcode numbers."""
        res1 = res2 = 0
        try:
            if hasattr(settings, 'SUBSCRIPTION_INPUT_BARCODE') and not (data is None):
                # Below code no more useful since LGDS use random barcode
                # import re
                # p = '0420(......)00.'
                # exp = re.compile(p)
                # match = exp.match(data['m_barcode'])
                # base = int(match.group(1))
                f = {'barcode': data['m_barcode']}
                res1 = self.o_api.execute('res.partner', 'generate_base', [self.id])
                res2 = self.o_api.update('res.partner', [self.id], f)

            else:
                res1 = self.o_api.execute('res.partner', 'generate_base', [self.id])
                res2 = self.generate_barcode()
        except Exception as e:
            print(str(e))
        return (res1 and res2)

    def create_capital_subscription_invoice(self, amount, date):
        """Make CapitalFundraisingWizard entities creation."""
        api = OdooAPI()
        f1 = {'type': 'out_invoice',
              'date_invoice': date,
              'journal_id': settings.CAP_JOURNAL_ID,
              'account_id': settings.CAP_APPELE_NON_VERSE_ACCOUNT_ID,
              # 'payment_term_id':
              'partner_id': self.id,
              'is_capital_fundraising': True,
              'fundraising_category_id': settings.FUNDRAISING_CAT_ID,
              'state': 'open'
              }
        invoice_id = api.create('account.invoice', f1)
        f2 = {'invoice_id': invoice_id,
              'uom_id': settings.UNITE_UOM_ID,
              'product_id': settings.PARTS_A_PRODUCT_ID,
              'price_unit': settings.PARTS_A_PRICE_UNIT,
              'name': 'Parts A',
              'quantity': int(int(amount) / settings.PARTS_A_PRICE_UNIT),
              'account_id': settings.CAP_INVOICE_LINE_ACCOUNT_ID
              }
        invoice_line_id = api.create('account.invoice.line', f2)
        api.execute('account.invoice',
                    'action_move_create',
                    [invoice_id])
        api.execute('account.invoice',
                    'assign_ownshare_to_invoice',
                    [invoice_id])
        return [invoice_id, invoice_line_id]

    def create_coop_shift_subscription(self, shift_t_id, stype, call_nb=1):
        """Store coop shift subscription."""
        # Get shift template ticket corresponding to given shift temp. id
        sti = None
        shift_type = 'standard'
        if stype == 2:
            shift_type = 'ftop'
        cond = [['shift_template_id', '=', int(shift_t_id)],
                ['shift_type', '=', shift_type]]
        fields = ['seats_reserved', 'seats_max']
        stt = self.o_api.search_read('shift.template.ticket', cond, fields)
        if len(stt) > 0:
            sti = stt[0]['id']  # shift_ticket_id
            seats_reserved = int(stt[0]['seats_reserved'])
            seats_max = int(stt[0]['seats_max'])
            # if (seats_reserved == seats_max):
            # TODO
        if not (sti is None):
            try:
                today = datetime.date.today().strftime("%Y-%m-%d")
                st_r_fields = {'partner_id': self.id,
                               'shift_template_id': int(shift_t_id),
                               'shift_ticket_id': int(sti),
                               'state': 'open',
                               'date_begin': today
                               }
                st_r_id = self.o_api.create('shift.template.registration',
                                            st_r_fields)
            except Exception as ex:
                if 'seules les inscriptions ABCD sont possibles' in str(ex) and call_nb < 2:
                    return self.create_coop_shift_subscription(shift_t_id, 1, call_nb + 1)
                else:
                    coop_logger.error("Error while creating shift.template.registration : %s, (fields =%s)", str(ex), str(st_r_fields))
                    st_r_id = None

        return st_r_id

    @staticmethod
    def exists(mail):
        api = OdooAPI()
        cond = [['email', 'ilike', str(mail)]]
        fields = ['email']
        res = api.search_read('res.partner', cond, fields, 1, 0, 'id DESC')
        if (res and len(res) == 1):
            answer = True
        else:
            answer = False
        return answer

    @staticmethod
    def is_associated(id_parent):
        api = OdooAPI()
        cond = [['parent_id', '=', int(id_parent)]]
        fields = ['id','name','parent_id','birthdate']
        res = api.search_read('res.partner', cond, fields, 10, 0, 'id DESC')
        already_have_adult_associated = False
        for partner in res:
            birthdate = partner['birthdate']
            if(birthdate):
                today = date.today()
                date1 = datetime.datetime.strptime(birthdate, "%Y-%m-%d")
                age = today.year - date1.year - ((today.month, today.day) < (date1.month, date1.day))
                if age > 17 :
                    already_have_adult_associated = True
        return already_have_adult_associated

    @staticmethod
    def finalize_coop_creation(post_data):
        """ Update coop data. """
        res = {}
        # First, update couchdb data
        c_db = CouchDB(arg_db='member')
        shift_template = json.loads(post_data['shift_template'])
        received_data = {'birthdate': post_data['birthdate'],
                         'city': post_data['city'],
                         'zip': post_data['zip'],
                         'firstname': post_data['firstname'],
                         'lastname': post_data['lastname'],
                         'odoo_id': post_data['odoo_id'],
                         'address': post_data['address'],
                         'mobile': post_data['mobile'],
                         'country': post_data['country'],
                         'validation_state': 'done'
                         }
        if ('sex' in post_data):
            received_data['sex'] = post_data['sex']
        if ('function' in post_data):
            received_data['function'] = post_data['function']
        if 'street2' in post_data:
            received_data['street2'] = post_data['street2']
        if 'phone' in post_data:
            received_data['phone'] = format_phone_number(post_data['phone'])
        r = c_db.updateDoc(received_data, 'odoo_id')
        if r:
            if ('odoo_id' in r):
                try:
                    # Update res.partner with received data
                    partner_id = CagetteMember.create_from_buffered_data(received_data)

                except Exception as e:
                    res['error'] = 'Erreur maj membre dans Odoo'
                    coop_logger.error("Pb avec couchDB (1): %s \n %s", str(received_data), str(e))
        else:
            res['error'] = 'Pb avec couchDB'
            coop_logger.error("Pb avec couchDB (B): %s", str(received_data))
        return res

    @staticmethod
    def latest_coop_id():
        """Return the last barcode base recorded in Odoo database."""
        api = OdooAPI()
        cond = [['is_member', '=', True],
                ['total_partner_owned_share', '>', 0]]
        fields = ['barcode_base']
        return api.search_read('res.partner', cond, fields, 1, 0, 'id DESC')

    @staticmethod
    def create_from_buffered_data(post_data):
        """
            Create member or update its data in odoo and return its partner_id.
            At creation:
             Capital subscription and shift subscription will be stored!
             Fill accounting envelops with payment data.
             Send welcome mail for new members.

        """
        # WARNING : Very sensitive data
        #           Very touching step :
        #           couchdb data should reflect Odoo state
        res = {}
        api = OdooAPI()
        c_db = CouchDB(arg_db='member')
        partner_id = None
        name_sep = getattr(settings, 'SUBSCRIPTION_NAME_SEP', ' ')
        ask_4_sex = getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False)
        ask_4_job = getattr(settings, 'SUBSCRIPTION_ASK_FOR_JOB', False)
        concat_order = getattr(settings, 'CONCAT_NAME_ORDER', 'FL')
        sex = 'o'
        function = ''

        if ask_4_sex is True:
            sex = post_data['sex']
        if ask_4_job is True:
            function = post_data['function']

        # With input type="date", transmitted value is YYYY-mm-dd
        # But, it could be dd/mm/YYYY if not supported by browser
        well_formatted_dob = True
        birthdate = post_data['birthdate']
        try:
            i = birthdate.index('/')
            b_elts = birthdate.split('/')
            birthdate = b_elts[2] + '-' + b_elts[1] + '-' + b_elts[0]
        except:
            # Birthdate should be '-' separated values
            try:
                i = birthdate.index('-')
            except:
                well_formatted_dob = False

        if (well_formatted_dob is True):
            # Prepare data for odoo
            if concat_order == 'LF':
                name = post_data['lastname'] + name_sep + post_data['firstname']
            else:
                name = post_data['firstname'] + name_sep + post_data['lastname']
            f = {'name': name,
                 'birthdate': birthdate,
                 'sex': sex,
                 'street': post_data['address'],
                 'zip': post_data['zip'],
                 'city': post_data['city'],
                 'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
                 'barcode_rule_id': settings.COOP_BARCODE_RULE_ID,
                 'function': function
                 }
            if ('_id' in post_data):
                f['email'] = post_data['_id']
            if ('country' in post_data):
                if (post_data['country'].lower() == 'france' or
                   post_data['country'] == ''):
                    f['country_id'] = 76
            if 'street2' in post_data:
                f['street2'] = post_data['street2']
            if ('phone' in post_data) and len(post_data['phone']) > 0:
                if len(f['phone']) == 0:
                    f['phone'] = format_phone_number(post_data['phone'])
                else:
                    f['mobile'] = f['phone']
                    f['phone'] = format_phone_number(post_data['phone'])

            # Create coop
            if not ('odoo_id' in post_data):
                partner_id = api.create('res.partner', f)
                try:
                    id = int(partner_id)
                except:
                    id = 0
                if (id > 0):    # Coop succesfuly created
                    # Update couchdb (rest of data updated on client side)
                    update_data = {'_id': post_data['_id'],
                                   'email': post_data['_id'],
                                   'odoo_id': id}

                    r = c_db.updateDoc(update_data, '_id')
                    if r:
                        # Check if we proceed to capital subscription
                        m = CagetteMember(partner_id)
                        owned_share = 0
                        existing_data = m.get_data()

                        if (len(existing_data) > 0):
                            owned_share = existing_data[0]['total_partner_owned_share']

                        if (owned_share > 0):
                            # form has already been submitted
                            # Subscription, shift and payment data won't be saved
                            res['subs'] = [-1, -1]
                            res['bc'] = False
                            res['shift'] = None
                            res['envelop'] = None
                        else:
                            # New member
                            # Create capital subscription, base & barcode
                            today = datetime.date.today().strftime("%Y-%m-%d")
                            res['subs'] = \
                                m.create_capital_subscription_invoice(post_data['shares_euros'], today)
                            res['bc'] = m.generate_base_and_barcode(post_data)

                            # if the new member is associated with an already existing member 
                            # then we put the state in "associated" and we create the "associated" member
                            if 'is_associated_people' in post_data and 'parent_id' in post_data :
                                fields = {}
                                fields['cooperative_state'] = 'associated'
                                api.update('res.partner', [partner_id], fields)
                                associated_member = {
                                    'email': post_data['_id'],
                                    'name': name,
                                    'birthdate': birthdate,
                                    'sex': sex,
                                    'street': post_data['address'],
                                    'zip': post_data['zip'],
                                    'city': post_data['city'],
                                    'phone': format_phone_number(post_data['mobile']), # Because list view default show Phone and people mainly gives mobile
                                    'barcode_rule_id': settings.ASSOCIATE_BARCODE_RULE_ID,
                                    'parent_id' : post_data['parent_id'],
                                    'is_associated_people': True,
                                    'function': function
                                    }
                                associated_member_id = api.create('res.partner', associated_member)
                                am = CagetteMember(associated_member_id)
                                res['bca'] = am.generate_base_and_barcode(post_data)
                            # If it's an new associated member with a new partner. Link will be made by the user in BDM/admin
                            # We add the associated member to the "associate" shift template so we can find them in Odoo
                            elif 'is_associated_people' not in post_data or 'is_associated_people' in post_data and 'parent_id' not in post_data:
                                # Create shift suscription if is not associated
                                shift_template = json.loads(post_data['shift_template'])
                                shift_t_id = shift_template['data']['id']
                                stype = shift_template['data']['type']
                                res['shift'] = \
                                    m.create_coop_shift_subscription(shift_t_id, stype)
                                # m.add_first_point(stype) # Not needed anymore

                            # Update couchdb do with new data
                            try:
                                updated_data = m.get_data()
                                update_data['barcode_base'] = updated_data[0]['barcode_base']
                                res['bc'] = update_data['barcode_base']
                                update_data['odoo_state'] = 'done'
                                c_db.updateDoc(update_data, '_id')
                            except Exception as e:
                                res['error'] = 'Erreur après souscription du capital'
                                coop_logger.error("Erreur après souscription : %s \n %s", str(res), str(e))


                            # Create or update envelop(s) with coop payment data
                            payment_data = {
                                'partner_id': partner_id,
                                'partner_name': post_data['firstname'] + ' ' + post_data['lastname'],
                                'payment_meaning': post_data['payment_meaning'],
                                'shares_euros': post_data['shares_euros'],
                                'checks_nb': post_data['checks_nb']     # is 0 if payment is cash
                            }

                            if ('checks' in post_data):
                                payment_data['checks'] = json.loads(post_data['checks'])
                            else:
                                payment_data['checks'] = []

                            if payment_data['payment_meaning'] == 'cash' or payment_data['payment_meaning'] == 'ch':
                                res['envelop'] = CagetteEnvelops().create_or_update_envelops(payment_data)
                            else:
                                p_data = {'partner_id': partner_id, 'type': payment_data['payment_meaning'], 'amount': post_data['shares_euros']}
                                res['envelop'] = CagetteEnvelops().save_payment(p_data)

                        # Send welcome mail
                        try:
                            api.execute('res.partner', 'send_welcome_email', [partner_id])
                        except Exception as e:
                            res['error'] = 'Erreur envoie mail invitation'
                        # from outils.common import CagetteMail
                        # try:
                        #     CagetteMail.sendWelcome(f['email'])
                        # except Exception as e:
                        #     res['error'] = 'Erreur envoie mail invitation'
                    else:
                        res['error'] = 'Pb avec couchDB'
                        res['data'] = update_data
                        coop_logger.error("Pb couchDB (C) : %s", str(res))
                else:
                    res['error'] = 'Erreur creation membre odoo'
                    coop_logger.error("Pb couchDB (D) : %s", str(res))
            # Update coop data
            else:
                odoo_id = int(post_data['odoo_id'])
                if (api.update('res.partner', [odoo_id], f) is True):
                    partner_id = odoo_id

        return partner_id

    @staticmethod
    def create_from_cvs_row(data):
        """
            Create member or update its data in odoo and return its partner_id.
            At creation:
             Capital subscription
        """
        res = {}
        api = OdooAPI()
        partner_id = None

        well_formatted_dob = True
        birthdate = data['date de naissance']
        try:
            i = birthdate.index('/')
            b_elts = birthdate.split('/')
            birthdate = b_elts[2] + '-' + b_elts[1] + '-' + b_elts[0]
        except:
            # Birthdate should be '-' separated values
            try:
                i = birthdate.index('-')
            except:
                well_formatted_dob = False

        if (well_formatted_dob is True):
            # Prepare data for odoo
            f = {'name': data['Prénom'] + ' ' + data['Nom'],
                 'birthdate': birthdate,
                 'sex': 'o',
                 'street': data['adresse rue'],
                 'zip': data['code postal'],
                 'city': data['ville'],
                 'country_id': 76,
                 'phone': data['tel'],
                 'email': data['mail'],
                 'barcode_rule_id': settings.COOP_BARCODE_RULE_ID
                 }
            partner_id = api.create('res.partner', f)
            try:
                id = int(partner_id)
            except:
                id = 0
            if (id > 0):    # Coop succesfuly created
                res['id'] = partner_id
                m = CagetteMember(partner_id)
                shares_euros = int(float(data['Nb de parts']) * 10)
                res['subs'] = m.create_capital_subscription_invoice(shares_euros, data['date inscription'])
                res['bc'] = m.generate_base_and_barcode(data)
            else:
                res['error'] = 'Unable to create member from ' + str(data)
        return res

    @staticmethod
    def store_warning_msg(post_data):
        """Store in couchDB database the warning new coop has written."""
        c_db = CouchDB(arg_db='member')
        data = {
            '_id': post_data['_id'],
            'coop_msg': post_data['msg']
        }
        r = c_db.updateDoc(data, '_id')
        return r

    @staticmethod
    def get_couchdb_data(email):
        """Retrieve couchDB data corresponding to given email."""
        c_db = CouchDB(arg_db='member')
        try:
            doc = c_db.getDocById(email)
        except:
            doc = []
        return doc

    @staticmethod
    def get_state_fr(coop_state):
        """Return french version of given coop_state."""
        company = getattr(settings, 'COMPANY_CODE', '')

        if coop_state == 'alert':
            fr_state = 'En alerte'
        elif coop_state == 'delay':
            fr_state = 'Délai accordé'
        elif coop_state == 'suspended':
            if company == 'lacagette':
                fr_state = 'Rattrapage'
            else:
                fr_state = 'Suspendu(e)'
        elif coop_state == 'not_concerned':
            fr_state = 'Non concerné(e)'
        elif coop_state == 'blocked':
            fr_state = 'Bloqué(e)'
        elif coop_state == 'unpayed':
            fr_state = 'Impayé constaté'
        elif coop_state == 'unsubscribed':
            fr_state = 'Désinscrit(e)'
        elif coop_state == 'up_to_date':
            fr_state = 'A jour'
        elif coop_state == 'exempted':
            fr_state = 'Exempté(e)'
        elif coop_state == 'associated':
            fr_state = 'En binôme'
        elif coop_state == 'gone':
            fr_state = 'Parti(e)'
        else:
            fr_state = 'Inconnu'
        return fr_state

    @staticmethod
    def get_members_next_shift(ids):
        """Retrieve next shift for given members ids."""
        api = OdooAPI()
        cond = [['partner_id', 'in', ids],
                ['date_begin', '>=', datetime.datetime.now().isoformat()],
                ['state', '=', 'open']]
        fields = ['shift_type', 'date_begin', 'partner_id', 'date_end', 'shift_ticket_id']

        res = api.search_read('shift.registration', cond, fields, 2500, 0, 'date_begin ASC')
        shifts = {}
        locale.setlocale(locale.LC_ALL, 'fr_FR.utf8')

        if len(res) > 0:
            local_tz = pytz.timezone('Europe/Paris')
            for s in res:
                date, t = s['date_begin'].split(' ')
                year, month, day = date.split('-')
                hour, minute, second = t.split(':')
                if int(hour) < 21:
                    start = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second), tzinfo=pytz.utc)
                    start_date = start.astimezone(local_tz)
                    s['start'] = start_date.strftime("%A %d %B %Y à %Hh%M")
                    if not (s['partner_id'][0] in shifts):
                        shifts[s['partner_id'][0]] = []
                    shifts[s['partner_id'][0]].append(s)

        return shifts

    @staticmethod
    def add_next_shifts_to_members(members):
        """Next shifts are added to members data."""
        ids = []
        m_list = []
        for m in members:
            ids.append(m['id'])

        shifts = CagetteMember.get_members_next_shift(ids)

        for m in members:
            s = []
            if m['id'] in shifts:
                s = shifts[m['id']]
            m['shifts'] = s
            m_list.append(m)
        return m_list

    @staticmethod
    def search(k_type, key, shift_id=None, search_type="full"):
        """Search member according 3 types of key."""
        api = OdooAPI()
        if k_type == 'id':
            cond = [['id', '=', int(key)]]
        elif k_type == 'barcode_base':
            cond = [['barcode_base', '=', str(key)]]
        elif k_type == 'barcode':
            cond = [['barcode', '=', str(key)]]
        else:
            cond = [['name', 'ilike', str(key)]]
        cond.append('|')
        cond.append(['is_member', '=', True])
        if search_type != 'members':
            cond.append(['is_associated_people', '=', True])
        else:
            cond.append(['is_associated_people', '=', False])
        cond.append(['cooperative_state', '!=', 'associated'])
        # cond.append(['cooperative_state', '!=', 'unsubscribed'])
        if search_type == "full" or search_type == 'members' or search_type == "manage_shift_registrations":
            fields = CagetteMember.m_default_fields
            if not shift_id is None:
                CagetteMember.m_default_fields.append('tmpl_reg_line_ids')
            res = api.search_read('res.partner', cond, fields)
            members = []
            if len(res) > 0:
                for m in res:
                    keep_it = False
                    if not shift_id is None and len(shift_id) > 0:
                        # Only member registred to shift_id will be returned
                        if len(m['tmpl_reg_line_ids']) > 0:
                            cond = [['id', '=', m['tmpl_reg_line_ids'][0]]]
                            fields = ['shift_template_id']
                            shift_templ_res = api.search_read('shift.template.registration.line', cond, fields)
                            if (len(shift_templ_res) > 0
                                and
                                int(shift_templ_res[0]['shift_template_id'][0]) == int(shift_id)):
                                keep_it = True
                    else:
                        keep_it = True
                    if keep_it is True:
                        try:
                            img_code = base64.b64decode(m['image_medium'])
                            extension = imghdr.what('', img_code)
                            m['image_extension'] = extension
                        except Exception as e:
                            coop_logger.info("Img error : %s", e)
                        m['state'] = m['cooperative_state']
                        m['cooperative_state'] = \
                            CagetteMember.get_state_fr(m['cooperative_state'])
                        # member = CagetteMember(m['id'], m['email'])
                        # m['next_shifts'] = member.get_next_shift()
                        if not m['parent_name'] is False:
                            m['name'] += ' (suppléant.e de son binôme ' + m['parent_name'] + ')'
                            del m['parent_name']
                        members.append(m)

            return CagetteMember.add_next_shifts_to_members(members)
        elif search_type == "makeups_data":
            fields = CagetteMember.m_short_default_fields
            fields = fields + ['shift_type', 'makeups_to_do', 'display_ftop_points', 'display_std_points', 'shift_type']
            cond.append(['shift_type', '=', 'standard'])
            res = api.search_read('res.partner', cond, fields)
            CagetteMembers.add_makeups_to_come_to_member_data(res)
            return res
        elif search_type == "shift_template_data":
            fields = CagetteMember.m_short_default_fields
            fields = fields + ['id', 'makeups_to_do', 'cooperative_state','parent_name']
            res = api.search_read('res.partner', cond, fields)

            if res:
                for partner in res:
                    c = [['partner_id', '=', int(partner['id'])], ['state', 'in', ('draft', 'open')]]
                    f = ['shift_template_id']
                    shift_template_reg = api.search_read('shift.template.registration', c, f)

                    if shift_template_reg:
                        partner['shift_template_id'] = shift_template_reg[0]['shift_template_id']
                    else:
                        partner['shift_template_id'] = None
                    if not partner['parent_name'] is False:
                        partner['name'] += ' (suppléant.e de son binôme ' + partner['parent_name'] + ')'
                        del partner['parent_name']

            return res
        else:
            # TODO differentiate short & subscription_data searches
            fields = CagetteMember.m_short_default_fields
            fields = fields + ['total_partner_owned_share','amount_subscription']
            res = api.search_read('res.partner', cond, fields)
            return res

    @staticmethod
    def remove_data_from_CouchDB(request):
        res = {}
        try:
            email = request.POST.get("email","")
            if len(email) > 0:
                is_connected_user = CagetteUser.are_credentials_ok(request)
                can_be_deleted = False
                coop = CagetteMember.retrieve_data_according_keys({'email':email})
                if len(coop) > 0:
                    coop = coop[0]
                    if (len(coop['tmpl_reg_line_ids']) > 0 and coop['total_partner_owned_share']):
                        #no need to be connected to delete it
                        can_be_deleted = True
                    else:
                        if not is_connected_user:
                            message = 'L\'inscription de ce membre n\'a pas été correctement finalisée, il ne peut être archivé.' + "\n"
                            if len(coop['tmpl_reg_line_ids']) == 0:
                                message += 'Il n\'est inscrit à aucun créneau. Veuillez en ajouter un sur Odoo.'+ "\n"
                            if coop['total_partner_owned_share'] == 0:
                                message += 'Le capital souscrit n\'a pas été enregistré. Veuillez le faire sur Odoo.'
                            res['msg'] = message

                else:
                    if is_connected_user is False:
                        res['msg'] = 'Aucun membre connu avec cet email'

                # if demand comes from a connected user, it can be deleted
                if is_connected_user:
                    can_be_deleted = True
                else:
                    if can_be_deleted is False:
                        res['msg'] = 'Seul un utilisateur connecté peut faire cette suppression'

                if can_be_deleted is True:
                    c_db = CouchDB(arg_db='member')
                    doc = c_db.getDocById(email)    # email is the id for members doc
                    res['action'] = c_db.delete(doc)
            else:
                res['msg'] = 'No email'
        except Exception as e:
            coop_logger.error("Remove data from couchDB : %s", str(e))
            res['msg'] = 'Oups ! Erreur Remove data from couchDB'
        return res

    @staticmethod
    def remove_from_mess_list(request):
        res = {}
        try:
            _id = request.POST.get("id", "")
            c_db = CouchDB(arg_db='member_mess')
            doc = c_db.getDocById(_id)
            res['action'] = c_db.delete(doc)
        except Exception as e:
            res['error'] = str(e)
        return res

    def search_associated_people(self):
        """ Search for an associated partner """
        res = {}

        c = [["parent_id", "=", self.id]]
        f = ["id", "name", "barcode_base"]

        res = self.o_api.search_read('res.partner', c, f)

        try:
            return res[0]
        except:
            return None

    def update_member_makeups(self, member_data):
        api = OdooAPI()
        res = {}

        f = { 'makeups_to_do': int(member_data["target_makeups_nb"]) }
        res_item = api.update('res.partner', [self.id], f)
        res = {
            'mid': self.id,
            'update': res_item
        }

        return res

    def get_member_selected_makeups(self):
        res = {}

        c = [["partner_id", "=", self.id], ["is_makeup", "=", True], ["state", "=", "open"]]
        f=['id']
        res = self.o_api.search_read("shift.registration", c, f)
        return res

    def is_member_registered_to_makeup_shift_template(self, shift_template_id):
        """ Given a shift template, check if the member is registered to a makeup on this shift template """
        try:
            c = [["partner_id", "=", self.id], ["is_makeup", "=", True], ["state", "=", "open"]]
            f=['shift_id']
            res_shift_ids = self.o_api.search_read("shift.registration", c, f)

            if res_shift_ids:
                shift_ids = [int(d['shift_id'][0]) for d in res_shift_ids]

                c = [["id", "in", shift_ids]]
                f=['shift_template_id']
                stis = self.o_api.search_read("shift.shift", c, f)

                for sti in stis:
                    if sti['shift_template_id'][0] == shift_template_id:
                        return True
        except Exception as e:
            print(str(e))

        return False

    def unsubscribe_member(self, changing_shift = False):
        """ If changing_shift, don't delete makeups registrations & don't close extension """
        res = {}

        now = datetime.datetime.now().isoformat()

        # Get and then delete shift template registration
        c = [['partner_id', '=', self.id]]
        f = ['id']
        res_ids = self.o_api.search_read("shift.template.registration", c, f)
        ids = [d['id'] for d in res_ids]

        if ids:
            res["delete_shift_template_reg"] = self.o_api.execute('shift.template.registration', 'unlink', ids)
        
        # Get and then delete shift registrations
        c = [['partner_id', '=', self.id], ['date_begin', '>', now]]
        if changing_shift is True:
            c.append(['is_makeup', '!=', True])
        f = ['id']
        res_ids = self.o_api.search_read("shift.registration", c, f)
        ids = [d['id'] for d in res_ids]
        
        if ids:
            res["delete_shifts_reg"]  = self.o_api.execute('shift.registration', 'unlink', ids)

        if changing_shift is False:
            # Close extensions if just unsubscribing, else keep it
            res["close_extensions"] = self.close_extension()

        return res

    def set_cooperative_state(self, state):
        f = {'cooperative_state': state}
        return self.o_api.update('res.partner', [self.id], f)

    def update_extra_shift_done(self, value):
        api = OdooAPI()
        res = {}

        f = { 'extra_shift_done': value }
        res_item = api.update('res.partner', [self.id], f)
        res = {
            'mid': self.id,
            'update': res_item
        }

        return res

    def close_extension(self):
        now = datetime.datetime.now().isoformat()

        c = [['partner_id', '=', self.id], ['date_start', '<=', now], ['date_stop', '>=', now]]
        f = ['id']
        res_ids = self.o_api.search_read("shift.extension", c, f)
        ids = [d['id'] for d in res_ids]
        
        if ids:
            f = {'date_stop': now}
            return self.o_api.update('shift.extension', ids, f)
        else:
            return False

class CagetteMembers(models.Model):
    """Class to manage operations on all members or part of them."""

    @staticmethod
    def get_problematic_members():
        """Search partner with missing elements"""
        api = OdooAPI()
        return []


    @staticmethod
    def raw_search(needle):
        """Search partner with missing elements"""
        res = []
        try:
            api = OdooAPI()
            cond = ['|', ('email', 'ilike', needle), ('display_name', 'ilike', needle)]
            fields = ['barcode_base', 'barcode', 'create_date',
                      'cooperative_state', 'name', 'birthdate', 'street', 'street2',
                      'zip', 'city', 'email', 'mobile', 'phone', 'total_partner_owned_share',
                      'amount_subscription', 'active_tmpl_reg_line_count',
                      'shift_type', 'current_template_name', 'sex']
            api_res = api.search_read('res.partner', cond, fields)
            if api_res:
                ids_in_env = CagetteEnvelops().get_ids_in_all()
                for c in api_res:
                    if not (str(c['id']) in ids_in_env):
                        c['envelops'] = False
                    else:
                        c['envelops'] = True
                res = api_res
        except Exception as e:
            res['error'] = str(e)
        return res

    @staticmethod
    def verify_subscription_state():
        """Verify couchDB and Odoo DB coherence."""
        c_db = CouchDB(arg_db='member')
        # Get all members with unregistrated shifts
        unregistrated_members = c_db.getAllDocs('odoo_state', 'done', False)
        partner_ids = []
        to_modify_in_couchDB = []
        for m in unregistrated_members:
            if 'odoo_id' in m:
                partner_ids.append(int(m['odoo_id']))
        if len(partner_ids) > 0:
            api = OdooAPI()
            cond = [['id', 'in', partner_ids]]
            f = ['total_partner_owned_share', 'name', 'barcode_base', 'tmpl_reg_line_ids']
            res = api.search_read('res.partner', cond, f)
            for p in res:
                if p['total_partner_owned_share'] > 0:
                    to_modify_in_couchDB.append(p)
        if len(to_modify_in_couchDB) > 0:
            for p in to_modify_in_couchDB:
                if len(p['tmpl_reg_line_ids']) > 0:
                    r = {'odoo_id': p['id'], 'odoo_state': 'done', 'barcode_base': p['barcode_base']}
                    c_db.updateDoc(r, 'odoo_id', ['shift_template'])

        return to_modify_in_couchDB

    @staticmethod
    def update_couchdb_barcodes():
        c_db = CouchDB(arg_db='member')
        # Get all members with 'done' state
        all_done = c_db.getAllDocs('odoo_state', 'done')
        partner_ids = []
        to_modify_in_couchDB = []
        for m in all_done:
            partner_ids.append(int(m['odoo_id']))
        if len(partner_ids) > 0:
            api = OdooAPI()
            cond = [['id', 'in', partner_ids]]
            f = ['barcode_base']
            res = api.search_read('res.partner', cond, f)

            for p in res:
                for m in all_done:
                    if (int(m['odoo_id']) == int(p['id'])):
                        try:
                            if int(m['barcode_base']) != int(p['barcode_base']):
                                m['barcode_base'] = p['barcode_base']
                                to_modify_in_couchDB.append(m)
                        except:
                            pass
            if len(to_modify_in_couchDB) > 0:
                for p in to_modify_in_couchDB:
                    r = {'odoo_id': p['odoo_id'],
                         'barcode_base': p['barcode_base']}
                    c_db.updateDoc(r, 'odoo_id')

        return to_modify_in_couchDB

    @staticmethod
    def _generate_inra_csv_data(odoo_result):
        data = {'lines': [], 'sum_up': ''}
        current_p = ''
        try:
            if (('purchases' in odoo_result) and len(odoo_result['purchases']) > 0):
                off = OFF()
                off_products = off.get_products()
                headers = ['date', 'coop_id', 'coop_num', 'coop_naissance', 'coop_ville',
                           'code-barre', 'nom_produit', 'qte', 'prix', 'remise',
                           'categ_id', 'nom_cat_cagette',
                           'off_qte', 'off_cat', 'off_labels',
                           'off_nutriscore', 'off_nova', 'off_nrj_100g',
                           'off_manufacture_places', 'off_origins']
                data['lines'].append(headers)
                coop_nums = []
                products = []
                remises = {}
                ca_ttc_panel = 0
                for p in odoo_result['purchases']:
                    current_p = p
                    if p['product_barcode'] in off_products:
                        off_pdt = off_products[p['product_barcode']]
                    else:
                        off_pdt = {'quantity': '', 'categories': '',
                                   'labels': '', 'nutrition_grade_fr': '', 'nova_group': '', 'energy_100g': '',
                                   'manufacturing_places': '', 'origins': ''}
                    pname = p['product_name'].replace(';', ' ')
                    cat_name = ''
                    if str(p['product_categ_id']) in odoo_result['pcat']:
                        cat_name = odoo_result['pcat'][str(p['product_categ_id'])]
                    line = [p['date_order'], p['coop_id'], p['coop_num'], p['coop_birthdate'], p['coop_city'],
                            p['product_barcode'], pname, p['product_qty'], p['product_price'], p['product_discount'],
                            p['product_categ_id'], cat_name,
                            off_pdt['quantity'], off_pdt['categories'], off_pdt['labels'],
                            off_pdt['nutrition_grade_fr'], off_pdt['nova_group'], off_pdt['energy_100g'],
                            off_pdt['manufacturing_places'], off_pdt['origins']]

                    ca_ttc_line = float(p['product_qty']) * float(p['product_price'])
                    if (int(p['product_discount']) > 0):
                        ca_ttc_line *= (100 - int(p['product_discount'])) / 100
                        if not (p['product_discount'] in remises):
                            remises[p['product_discount']] = 0
                        remises[p['product_discount']] += ca_ttc_line

                    ca_ttc_panel += ca_ttc_line
                    data['lines'].append(line)
                    if not (p['coop_num'] in coop_nums):
                        coop_nums.append(p['coop_num'])
                    if not (p['product_id'] in products):
                        products.append(p['product_id'])

                data['sum_up'] =  'Coopérateurs du panel ayant fait au moins 1 achat : ' + str(len(coop_nums)) + "\n"
                data['sum_up'] += 'Nb de références de produits achetés : ' + str(len(products)) + "\n"
                data['sum_up'] += 'CA TTC réalisé : ' + "{:.2f}".format(ca_ttc_panel) + "\n"
                for pc, val in remises.items():
                    data['sum_up'] += 'dont CA TTC remises ' + str(int(pc)) + '% : ' + "{:.2f}".format(val) + "\n"
        except Exception as e:
            data['error'] = str(e) + ' : produit en cours = ' + str(current_p)
        return data

    @staticmethod
    def get_inra_panel_purchases(request):
        api = OdooAPI()
        list_fpath = 'members/panel.csv'
        res = {'error': ''}
        try:
            nums = []
            with open(list_fpath) as fp:
                line = fp.readline()
                while line:
                    nums.append(line.strip())
                    line = fp.readline()
            if len(nums) > 0:
                params = {'partners_coop_num': nums}
                # num_slice = nums[0:1]
                month = request.POST.get('mois_month')
                year = request.POST.get('mois_year')
                try:
                    m = int(month)
                    y = int(year)
                    if (m < 10):
                        month = '0' + month
                    if (m > 0 and y > 0):
                        params['month'] = year + '-' + month
                    else:
                        today = datetime.date.today()
                        year = str(today.year)
                        month = str(today.month)
                        if (len(month) == 1):
                            month = '0' + month
                        params['month'] = year + '-' + month
                except Exception as e2:
                    res['error'] += str(e2)
                    pass
                odoo_result = api.execute('lacagette.pos_member_purchases', 'get_members_purchases', params)
                res['data'] = CagetteMembers._generate_inra_csv_data(odoo_result)
                if 'error' in res['data']:
                    res['error'] += res['data']['error']
                res['params'] = params

        except Exception as e:
            res['error'] += str(e)

        return res

    @staticmethod
    def get(cond, fields, o=0, l=5000):
        res = {}
        try:
            api = OdooAPI()
            res = api.search_read('res.partner', cond, fields, offset=o, limit=l)
        except Exception as e:
            res['error'] = str(e)
        return res

    @staticmethod
    def add_pts_to_everyone(mtype, ids, pts, reason):
        res = {}
        try:
            for mid in ids:
                m = CagetteMember(mid)
                res[str(mid)] = m.add_pts(mtype, pts, reason)
        except Exception as e:
            res['error'] = str(e)
        return res

    @staticmethod
    def get_makeups_members(ids=[]):
        api = OdooAPI()
        cond = [['makeups_to_do','>', 0]]

        if len(ids) > 0:
            cond.append(['id','in', ids])

        fields = ['id', 'name', 'display_std_points', 'display_ftop_points', 'shift_type', 'makeups_to_do', 'date_delay_stop']
        res = api.search_read('res.partner', cond, fields)

        # There are two things we need to do now :
        # 1 : fetching members with no makeups to do but with some makeups to come
        # 2 : providing makeups to come to all members

        cs = CagetteShift()
        makeups_to_come_per_partner = cs.get_partners_with_makeups_to_come()

        # 1 : fetching members with no makeups to do but with some makeups to come
        cond = [['makeups_to_do', '=', 0], ['id', 'in', list(makeups_to_come_per_partner.keys())]]

        if len(ids) > 0:
            cond.append(['id','in', ids])

        res = res + api.search_read('res.partner', cond, fields)

        # 2 : providing makeups to come to all members
        for idx, partner in enumerate(res):
            if partner['id'] in makeups_to_come_per_partner:
                res[idx]['makeups_to_come'] = makeups_to_come_per_partner[partner['id']]
            else:
                res[idx]['makeups_to_come'] = 0

        return res

    @staticmethod
    def add_makeups_to_come_to_member_data(res):
        if res:
            cs = CagetteShift()
            for idx, partner in enumerate(res):
                shift_data = cs.get_shift_partner(int(partner['id']))
                res[idx]['makeups_to_come'] = sum(1 for value in shift_data if value['is_makeup'])

    @staticmethod
    def get_attached_members():
        api = OdooAPI()
        cond = [['is_associated_people','=', True]]
        fields = ['id', 'name', 'parent_name']
        res = api.search_read('res.partner', cond, fields)
        return res

class CagetteUser(models.Model):

    @staticmethod
    def get_credentials(request):
        import hashlib

        data = {}
        api = OdooAPI()
        login = request.POST.get('login')
        password = request.POST.get('password')

        if login and password:
            uid = api.authenticate(login, password)
            if not(uid is False):
                cond = [['id', '=', uid]]
                fields = ['active', 'cooperative_state', 'create_date', 'groups_id']
                try:
                    res = api.search_read('res.users', cond, fields)
                    if (res[0]['active'] is True):
                        tocode = res[0]['create_date'] + request.META.get('HTTP_USER_AGENT')
                        data['authtoken'] = hashlib.sha256(tocode.encode('utf-8')).hexdigest()
                        data['uid'] = uid
                        data['cooperative_state'] = res[0]['cooperative_state']
                        cond = [['id', 'in', res[0]['groups_id']]]
                        fields = ['full_name']
                        data['groups'] = api.search_read('res.groups', cond, fields)

                except Exception as e:
                    data['error'] = str(e)

        return data

    @staticmethod
    def are_credentials_ok(request):
        import hashlib
        answer = False
        if 'authtoken' in request.COOKIES and 'uid' in request.COOKIES:
            api = OdooAPI()
            cond = [['id', '=', request.COOKIES['uid']]]
            fields = ['active','create_date']
            try:
                res = api.search_read('res.users', cond, fields)
                if (res[0]['active'] is True):
                    tocode = res[0]['create_date'] + request.META.get('HTTP_USER_AGENT')
                    calc_authtoken = hashlib.sha256(tocode.encode('utf-8')).hexdigest()
                    if calc_authtoken == request.COOKIES['authtoken']:
                        answer = True
            except:
                pass

        return answer


from shifts.models import CagetteShift