# -*- coding: utf-8; -*-
################################################################################
#
#  WuttaFarm --Web app to integrate with and extend farmOS
#  Copyright © 2026 Lance Edgar
#
#  This file is part of WuttaFarm.
#
#  WuttaFarm 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.
#
#  WuttaFarm 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
#  WuttaFarm.  If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Custom grid stuff for use with farmOS / JSONAPI
"""

import datetime

from wuttaweb.grids.filters import GridFilter


class SimpleFilter(GridFilter):

    default_verbs = ["equal", "not_equal"]

    def __init__(self, request, key, path=None, **kwargs):
        super().__init__(request, key, **kwargs)
        self.path = path or key

    def filter_equal(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, "=", value)
        return data

    def filter_not_equal(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, "<>", value)
        return data

    def filter_is_null(self, data, value):
        data.add_filter(self.path, "IS NULL", None)
        return data

    def filter_is_not_null(self, data, value):
        data.add_filter(self.path, "IS NOT NULL", None)
        return data


class StringFilter(SimpleFilter):

    default_verbs = ["contains", "equal", "not_equal"]

    def filter_contains(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, "CONTAINS", value)
        return data


class NullableStringFilter(StringFilter):

    default_verbs = ["contains", "equal", "not_equal", "is_null", "is_not_null"]


class IntegerFilter(SimpleFilter):

    default_verbs = [
        "equal",
        "not_equal",
        "less_than",
        "less_equal",
        "greater_than",
        "greater_equal",
    ]

    def filter_less_than(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, "<", value)
        return data

    def filter_less_equal(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, "<=", value)
        return data

    def filter_greater_than(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, ">", value)
        return data

    def filter_greater_equal(self, data, value):
        if value := self.coerce_value(value):
            data.add_filter(self.path, ">=", value)
        return data


class NullableIntegerFilter(IntegerFilter):

    default_verbs = ["equal", "not_equal", "is_null", "is_not_null"]


class BooleanFilter(SimpleFilter):

    default_verbs = ["is_true", "is_false"]

    def filter_is_true(self, data, value):
        data.add_filter(self.path, "=", 1)
        return data

    def filter_is_false(self, data, value):
        data.add_filter(self.path, "=", 0)
        return data


class NullableBooleanFilter(BooleanFilter):

    default_verbs = ["is_true", "is_false", "is_null", "is_not_null"]


# TODO: this may not work, it's not used anywhere yet
class DateFilter(SimpleFilter):

    data_type = "date"

    default_verbs = [
        "equal",
        "not_equal",
        "greater_than",
        "greater_equal",
        "less_than",
        "less_equal",
        # 'between',
    ]

    default_verb_labels = {
        "equal": "on",
        "not_equal": "not on",
        "greater_than": "after",
        "greater_equal": "on or after",
        "less_than": "before",
        "less_equal": "on or before",
        # "between": "between",
        "is_null": "is null",
        "is_not_null": "is not null",
        "is_any": "is any",
    }

    def coerce_value(self, value):
        if value:
            if isinstance(value, datetime.date):
                return value

            try:
                dt = datetime.datetime.strptime(value, "%Y-%m-%d")
            except ValueError:
                log.warning("invalid date value: %s", value)
            else:
                return dt.date()

        return None


# TODO: this is not very complete yet, so far used only for animal birthdate
class DateTimeFilter(DateFilter):

    default_verbs = ["equal", "is_null", "is_not_null"]

    def coerce_value(self, value):
        """
        Convert user input to a proper ``datetime.date`` object.
        """
        if value:
            if isinstance(value, datetime.date):
                return value

            try:
                dt = datetime.datetime.strptime(value, "%Y-%m-%d")
            except ValueError:
                log.warning("invalid date value: %s", value)
            else:
                return dt.date()

        return None

    def filter_equal(self, data, value):
        if value := self.coerce_value(value):

            start = datetime.datetime.combine(value, datetime.time(0))
            start = self.app.localtime(start, from_utc=False)

            stop = datetime.datetime.combine(
                value + datetime.timedelta(days=1), datetime.time(0)
            )
            stop = self.app.localtime(stop, from_utc=False)

            data.add_filter(self.path, ">=", int(start.timestamp()))
            data.add_filter(self.path, "<", int(stop.timestamp()))

        return data


class SimpleSorter:

    def __init__(self, key):
        self.key = key

    def __call__(self, data, sortdir):
        data.add_sorter(self.key, sortdir)
        return data


class ResourceData:

    def __init__(
        self,
        config,
        farmos_client,
        content_type,
        include=None,
        normalizer=None,
    ):
        self.config = config
        self.farmos_client = farmos_client
        self.entity, self.bundle = content_type.split("--")
        self.filters = []
        self.sorters = []
        self.include = include
        self.normalizer = normalizer
        self._data = None

    def __bool__(self):
        return True

    def __getitem__(self, subscript):
        return self.get_data()[subscript]

    def __len__(self):
        return len(self._data)

    def add_filter(self, path, operator, value):
        self.filters.append((path, operator, value))

    def add_sorter(self, path, sortdir):
        self.sorters.append((path, sortdir))

    def get_data(self):
        if self._data is None:
            params = {}

            i = 0
            for path, operator, value in self.filters:
                i += 1
                key = f"{i:03d}"
                params[f"filter[{key}][condition][path]"] = path
                params[f"filter[{key}][condition][operator]"] = operator
                params[f"filter[{key}][condition][value]"] = value

            sorters = []
            for path, sortdir in self.sorters:
                prefix = "-" if sortdir == "desc" else ""
                sorters.append(f"{prefix}{path}")
            if sorters:
                params["sort"] = ",".join(sorters)

            # nb. while the API allows for pagination, it does not
            # tell me how many total records there are (IIUC).  also
            # if i ask for e.g. items 21-40 (page 2 @ 20/page) i am
            # not guaranteed to get 20 items even if there are plenty
            # in the DB, since Drupal may filter some out based on
            # permissions.  (granted that may not be an issue in
            # practice, but can't rule it out.) so the punchline is,
            # we fetch "all" (sic) data and send it to the frontend,
            # and pagination happens there.

            # TODO: if we ever try again, this sort of works...
            # params["page[offset]"] = start
            # params["page[limit]"] = stop - start

            if self.include:
                params["include"] = self.include

            result = self.farmos_client.resource.get(
                self.entity, self.bundle, params=params
            )
            data = result["data"]
            included = {obj["id"]: obj for obj in result.get("included", [])}

            if self.normalizer:
                data = [self.normalizer(d, included) for d in data]

            self._data = data
        return self._data
