# -*- coding: utf-8 -*-
# © 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
"""Abstract model to show each relation from two sides."""
from psycopg2.extensions import AsIs

from openerp import _, api, fields, models
from openerp.tools import drop_view_if_exists


PADDING = 10
_RECORD_TYPES = [
    ('a', 'Left partner to right partner'),
    ('b', 'Right partner to left partner'),
]


class ResPartnerRelationAll(models.AbstractModel):
    """Abstract model to show each relation from two sides."""
    _auto = False
    _log_access = False
    _name = 'res.partner.relation.all'
    _description = 'All (non-inverse + inverse) relations between partners'
    _order = (
        'this_partner_id, type_selection_id,'
        'date_end desc, date_start desc'
    )

    _overlays = 'res.partner.relation'

    this_partner_id = fields.Many2one(
        comodel_name='res.partner',
        string='One Partner',
        required=True,
    )
    other_partner_id = fields.Many2one(
        comodel_name='res.partner',
        string='Other Partner',
        required=True,
    )
    type_selection_id = fields.Many2one(
        comodel_name='res.partner.relation.type.selection',
        string='Relation Type',
        required=True,
    )
    relation_id = fields.Many2one(
        comodel_name='res.partner.relation',
        string='Relation',
        readonly=True,
    )
    record_type = fields.Selection(
        selection=_RECORD_TYPES,
        string='Record Type',
        readonly=True,
    )
    date_start = fields.Date('Starting date')
    date_end = fields.Date('Ending date')
    active = fields.Boolean(
        string='Active',
        help="Records with date_end in the past are inactive",
    )
    any_partner_id = fields.Many2many(
        comodel_name='res.partner',
        string='Partner',
        compute=lambda self: None,
        search='_search_any_partner_id'
    )

    def _get_additional_view_fields(self):
        """
        append to this list if you added fields to res_partner_relation that
        you need in this model and related fields are not adequate
        (ie for sorting)
        You must use the same name as in res_partner_relation.

        def _get_additional_view_fields(self):
            res = super(
                ResPartnerRelationAll, self)._get_additional_view_fields()
            return res + ['my_field1', 'my_field2']
        """
        return []

    def _auto_init(self, cr, context=None):
        drop_view_if_exists(cr, self._table)
        additional_view_fields = ','.join(self._get_additional_view_fields())
        additional_view_fields = (',' + additional_view_fields)\
            if additional_view_fields else ''
        cr.execute(
            """\
CREATE OR REPLACE VIEW %(table)s AS
    SELECT
        rel.id * %(padding)s AS id,
        rel.id AS relation_id,
        cast('a' AS CHAR(1)) AS record_type,
        rel.left_partner_id AS this_partner_id,
        rel.right_partner_id AS other_partner_id,
        rel.date_start,
        rel.date_end,
        (rel.date_end IS NULL OR rel.date_end >= current_date) AS active,
        rel.type_id * %(padding)s AS type_selection_id
        %(additional_view_fields)s
    FROM res_partner_relation rel
    UNION SELECT
        rel.id * %(padding)s + 1,
        rel.id,
        CAST('b' AS CHAR(1)),
        rel.right_partner_id,
        rel.left_partner_id,
        rel.date_start,
        rel.date_end,
        rel.date_end IS NULL OR rel.date_end >= current_date,
        CASE
            WHEN typ.is_symmetric THEN rel.type_id * %(padding)s
            ELSE rel.type_id * %(padding)s + 1
        END
        %(additional_view_fields)s
    FROM res_partner_relation rel
    JOIN res_partner_relation_type typ ON (rel.type_id = typ.id)
            """,
            {
                'table': AsIs(self._table),
                'padding': PADDING,
                'additional_view_fields': AsIs(additional_view_fields),
            }
        )
        return super(ResPartnerRelationAll, self)._auto_init(
            cr, context=context
        )

    @api.model
    def _search_any_partner_id(self, operator, value):
        """Search relation with partner, no matter on which side."""
        # pylint: disable=no-self-use
        return [
            '|',
            ('this_partner_id', operator, value),
            ('other_partner_id', operator, value),
        ]

    @api.multi
    def name_get(self):
        return {
            this.id: '%s %s %s' % (
                this.this_partner_id.name,
                this.type_selection_id.display_name,
                this.other_partner_id.name,
            )
            for this in self
        }

    @api.onchange('type_selection_id')
    def onchange_type_selection_id(self):
        """Add domain on partners according to category and contact_type."""

        def check_partner_domain(partner, partner_domain, side):
            """Check wether partner_domain results in empty selection
            for partner, or wrong selection of partner already selected.
            """
            warning = {}
            if partner:
                test_domain = [('id', '=', partner.id)] + partner_domain
            else:
                test_domain = partner_domain
            partner_model = self.env['res.partner']
            partners_found = partner_model.search(test_domain, limit=1)
            if not partners_found:
                warning['title'] = _('Error!')
                if partner:
                    warning['message'] = (
                        _('%s partner incompatible with relation type.') %
                        side.title()
                    )
                else:
                    warning['message'] = (
                        _('No %s partner available for relation type.') %
                        side
                    )
            return warning

        this_partner_domain = []
        other_partner_domain = []
        if self.type_selection_id.contact_type_this:
            this_partner_domain.append((
                'is_company', '=',
                self.type_selection_id.contact_type_this == 'c'
            ))
        if self.type_selection_id.partner_category_this:
            this_partner_domain.append((
                'category_id', 'in',
                self.type_selection_id.partner_category_this.ids
            ))
        if self.type_selection_id.contact_type_other:
            other_partner_domain.append((
                'is_company', '=',
                self.type_selection_id.contact_type_other == 'c'
            ))
        if self.type_selection_id.partner_category_other:
            other_partner_domain.append((
                'category_id', 'in',
                self.type_selection_id.partner_category_other.ids
            ))
        result = {'domain': {
            'this_partner_id': this_partner_domain,
            'other_partner_id': other_partner_domain,
        }}
        # Check wether domain results in no choice or wrong choice of partners:
        warning = {}
        if this_partner_domain:
            warning = check_partner_domain(
                self.this_partner_id, this_partner_domain, _('this')
            )
        if not warning and other_partner_domain:
            warning = check_partner_domain(
                self.other_partner_id, other_partner_domain, _('other')
            )
        if warning:
            result['warning'] = warning
        return result

    @api.onchange(
        'this_partner_id',
        'other_partner_id',
    )
    def onchange_partner_id(self):
        """Set domain on type_selection_id based on partner(s) selected."""

        def check_type_selection_domain(type_selection_domain):
            """If type_selection_id already selected, check wether it
            is compatible with the computed type_selection_domain. An empty
            selection can practically only occur in a practically empty
            database, and will not lead to problems. Therefore not tested.
            """
            warning = {}
            if not (type_selection_domain and self.type_selection_id):
                return warning
            test_domain = (
                [('id', '=', self.type_selection_id.id)] +
                type_selection_domain
            )
            type_model = self.env['res.partner.relation.type.selection']
            types_found = type_model.search(test_domain, limit=1)
            if not types_found:
                warning['title'] = _('Error!')
                warning['message'] = _(
                    'Relation type incompatible with selected partner(s).'
                )
            return warning

        type_selection_domain = []
        if self.this_partner_id:
            type_selection_domain += [
                '|',
                ('contact_type_this', '=', False),
                ('contact_type_this', '=',
                 self.this_partner_id.get_partner_type()),
                '|',
                ('partner_category_this', '=', False),
                ('partner_category_this', 'in',
                 self.this_partner_id.category_id.ids),
            ]
        if self.other_partner_id:
            type_selection_domain += [
                '|',
                ('contact_type_other', '=', False),
                ('contact_type_other', '=',
                 self.other_partner_id.get_partner_type()),
                '|',
                ('partner_category_other', '=', False),
                ('partner_category_other', 'in',
                 self.other_partner_id.category_id.ids),
            ]
        result = {'domain': {
            'type_selection_id': type_selection_domain,
        }}
        # Check wether domain results in no choice or wrong choice for
        # type_selection_id:
        warning = check_type_selection_domain(type_selection_domain)
        if warning:
            result['warning'] = warning
        return result

    @api.model
    def _correct_vals(self, vals):
        """Fill left and right partner from this and other partner."""
        vals = vals.copy()
        if 'this_partner_id' in vals:
            vals['left_partner_id'] = vals['this_partner_id']
            del vals['this_partner_id']
        if 'other_partner_id' in vals:
            vals['right_partner_id'] = vals['other_partner_id']
            del vals['other_partner_id']
        if 'type_selection_id' not in vals:
            return vals
        selection = self.type_selection_id.browse(vals['type_selection_id'])
        type_id = selection.type_id.id
        is_inverse = selection.is_inverse
        vals['type_id'] = type_id
        del vals['type_selection_id']
        # Need to switch right and left partner if we are in reverse id:
        if 'left_partner_id' in vals or 'right_partner_id' in vals:
            if is_inverse:
                left_partner_id = False
                right_partner_id = False
                if 'left_partner_id' in vals:
                    right_partner_id = vals['left_partner_id']
                    del vals['left_partner_id']
                if 'right_partner_id' in vals:
                    left_partner_id = vals['right_partner_id']
                    del vals['right_partner_id']
                if left_partner_id:
                    vals['left_partner_id'] = left_partner_id
                if right_partner_id:
                    vals['right_partner_id'] = right_partner_id
        return vals

    @api.multi
    def write(self, vals):
        """divert non-problematic writes to underlying table"""
        vals = self._correct_vals(vals)
        for rec in self:
            rec.relation_id.write(vals)
        return True

    @api.model
    def create(self, vals):
        """Divert non-problematic creates to underlying table.

        Create a res.partner.relation but return the converted id.
        """
        is_inverse = False
        if 'type_selection_id' in vals:
            selection = self.type_selection_id.browse(
                vals['type_selection_id']
            )
            is_inverse = selection.is_inverse
        vals = self._correct_vals(vals)
        res = self.relation_id.create(vals)
        return_id = res.id * PADDING + (is_inverse and 1 or 0)
        return self.browse(return_id)

    @api.multi
    def unlink(self):
        """divert non-problematic creates to underlying table"""
        # pylint: disable=arguments-differ
        for rec in self:
            rec.relation_id.unlink()
        return True