# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

from sql import Column, Literal

from trytond import backend
from trytond.pool import Pool
from trytond.tools import grouped_slice

from .function import Function
from .many2one import Many2One


def fmany2one(
        name, sources, target, string="", ondelete='SET NULL', **kwargs):
    sources = sources.split(',')
    target_model, target_fields = target.split(',', 1)
    target_fields = target_fields.split(',')
    assert len(sources) == len(target_fields)

    def convert_value(value):
        pool = Pool()
        Target = pool.get(target_model)
        if isinstance(value, int):
            return getattr(Target(value), target_fields[0])

    class Mixin:
        __slots__ = ()

        @classmethod
        def __register__(cls, module):
            pool = Pool()
            Target = pool.get(target_model)
            table_h = cls.__table_handler__(module)
            target_h = Target.__table_handler__(module)
            super().__register__(module)
            if (all(table_h.column_exist(s) for s in sources)
                    and all(target_h.column_exist(t) for t in target_fields)):
                table_h.add_fk(
                    sources, Target._table, target_fields,
                    on_delete=getattr(cls, name).ondelete)

        @classmethod
        def __setup__(cls):
            super().__setup__()
            if any(isinstance(getattr(cls, s), Function) for s in sources):
                try:
                    delattr(Mixin, f'domain_{name}')
                except AttributeError:
                    pass

        @classmethod
        def preprocess_values(cls, mode, values):
            values = super().preprocess_values(mode, values)
            if name in values:
                values[sources[0]] = convert_value(values.pop(name))
            return values

    @classmethod
    def getter(cls, records, name):
        pool = Pool()
        Target = pool.get(target_model)
        values = set(filter(all,
                (tuple(getattr(r, s) for s in sources) for r in records)))
        values2id = {}
        for sub_values in grouped_slice(
                values, backend.MAX_QUERY_PARAMS // len(target_fields)):
            domain = ['OR']
            for values in sub_values:
                domain.append(
                    [(f, '=', v) for f, v in zip(target_fields, values)])
            targets = Target.search(domain)
            values2id.update(
                (tuple(getattr(t, f) for f in target_fields), t.id)
                for t in targets)
        return {r.id: values2id.get(
                tuple(getattr(r, s) for s in sources)) for r in records}

    @classmethod
    def setter(cls, records, name, value):
        cls.write(records, {sources[0]: convert_value(value)})

    @classmethod
    def searcher(cls, clause, tables):
        pool = Pool()
        Target = pool.get(target_model)
        table, _ = tables[None]
        if name not in tables:
            target = Target.__table__()
            join = Literal(True)
            for source, target_field in zip(sources, target_fields):
                join &= Column(table, source) == Column(target, target_field)
            tables[name] = {
                None: (target, join),
                }
        nested = clause[0][len(name) + 1:]
        if not nested:
            if isinstance(clause[2], str):
                nested = 'rec_name'
            else:
                nested = 'id'
        domain = [(nested, *clause[1:])]
        tables, clause = Target.search_domain(
            domain, tables=tables[name])
        return clause

    setattr(Mixin, name, Function(
            Many2One(target_model, string, ondelete=ondelete, **kwargs),
            f'get_{name}',
            setter=f'set_{name}'
            if not kwargs.get('readonly', False) else None))
    setattr(Mixin, f'get_{name}', getter)
    if not kwargs.get('readonly', False):
        setattr(Mixin, f'set_{name}', setter)
    setattr(Mixin, f'domain_{name}', searcher)
    return Mixin
