# Copyright 2017-2025 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from sqlalchemy import and_, or_, text

from ..models import (
    Domain,
    Email,
    ExternalAuthType,
    Group,
    GroupPolicy,
    Policy,
    RefreshToken,
    SAMLPysaml2Cache,
    SAMLSession,
    Tenant,
    User,
    UserGroup,
    UserPolicy,
)


class FilterExpr:
    def __call__(self, value):
        raise NotImplementedError


class AnyEquals(FilterExpr):
    def __init__(self, relationship, column):
        self.relationship = relationship
        self.column = column

    def __call__(self, value):
        return self.relationship.any(self.column == value)


class AnyIlike(FilterExpr):
    def __init__(self, relationship, column):
        self.relationship = relationship
        self.column = column

    def __call__(self, pattern):
        return self.relationship.any(self.column.ilike(pattern))


class SearchFilter:
    def __init__(self, *columns):
        self._columns = columns

    def new_filter(self, search=None, **ignored):
        if search is None:
            return text('true')

        pattern = self.new_pattern(search)
        conditions = []
        for column in self._columns:
            if callable(column):
                conditions.append(column(pattern))
            else:
                conditions.append(column.ilike(pattern))

        return or_(*conditions)

    def new_pattern(self, search):
        if not search:
            return '%'
        else:
            words = [w for w in search.split(' ') if w]
            return '%{}%'.format('%'.join(words))


class StrictFilter:
    def __init__(self, *column_configs):
        self._column_configs = column_configs

    def new_filter(self, **kwargs):
        filter_ = text('true')

        for key, column, type_ in self._column_configs:
            if key not in kwargs:
                continue

            value = type_(kwargs[key]) if type_ else kwargs[key]
            if callable(column):
                condition = column(value)
            else:
                condition = (
                    column.in_(value) if isinstance(value, list) else column == value
                )

            filter_ = and_(filter_, condition)

        return filter_


class _TenantStrictFilter(StrictFilter):
    def new_filter(self, domain_name=None, **kwargs):
        filter_ = super().new_filter(**kwargs)
        if domain_name:
            filter_ = and_(filter_, Tenant.domains.any(Domain.name == domain_name))
        return filter_


class FilterMixin:
    search_filter = SearchFilter()
    strict_filter = StrictFilter()

    def new_search_filter(self, **kwargs):
        return self.search_filter.new_filter(**kwargs)

    def new_strict_filter(self, **kwargs):
        return self.strict_filter.new_filter(**kwargs)


external_auth_strict_filter = StrictFilter(('type', ExternalAuthType.name, None))
group_strict_filter = StrictFilter(
    ('uuid', Group.uuid, str),
    ('name', Group.name, None),
    ('slug', Group.slug, None),
    ('tenant_uuid', Group.tenant_uuid, str),
    ('user_uuid', AnyEquals(Group.user_groups, UserGroup.user_uuid), str),
    ('read_only', Group.system_managed, bool),
)
policy_strict_filter = StrictFilter(
    ('uuid', Policy.uuid, str),
    ('name', Policy.name, None),
    ('slug', Policy.slug, None),
    ('user_uuid', AnyEquals(Policy.user_policies, UserPolicy.user_uuid), str),
    ('group_uuid', AnyEquals(Policy.group_policies, GroupPolicy.group_uuid), str),
    ('tenant_uuid', Policy.tenant_uuid, str),
)
refresh_token_strict_filter = StrictFilter(
    ('client_id', RefreshToken.client_id, None),
    ('created_at', RefreshToken.created_at, None),
    ('mobile', RefreshToken.mobile, None),
)
tenant_strict_filter = _TenantStrictFilter(
    ('uuid', Tenant.uuid, str),
    ('uuids', Tenant.uuid, list),
    ('name', Tenant.name, None),
    ('slug', Tenant.slug, None),
)
user_strict_filter = StrictFilter(
    ('uuid', User.uuid, str),
    ('username', User.username, None),
    ('firstname', User.firstname, None),
    ('lastname', User.lastname, None),
    ('purpose', User.purpose, None),
    ('email_address', AnyEquals(User.emails, Email.address), None),
    ('group_uuid', AnyEquals(User.user_groups, UserGroup.group_uuid), str),
)
saml_session_strict_filter = StrictFilter(
    ('session_id', SAMLSession.session_id, str),
    ('relay_state', SAMLSession.relay_state, str),
)

external_auth_search_filter = SearchFilter(ExternalAuthType.name)
group_search_filter = SearchFilter(Group.name)
policy_search_filter = SearchFilter(Policy.name, Policy.description)
tenant_search_filter = SearchFilter(
    Tenant.name,
    Tenant.slug,
    AnyIlike(Tenant.domains, Domain.name),
)
user_search_filter = SearchFilter(
    User.firstname,
    User.lastname,
    User.username,
    AnyIlike(User.emails, Email.address),
)
refresh_token_search_filter = SearchFilter(RefreshToken.client_id)
saml_pysaml2_cache_search_filter = SearchFilter(
    SAMLPysaml2Cache.name_id,
    SAMLPysaml2Cache.entity_id,
    SAMLPysaml2Cache.not_on_or_after,
)
