# -*- 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 app handler for WuttaFarm
"""

from wuttjamaican import app as base


class WuttaFarmAppHandler(base.AppHandler):
    """
    Custom :term:`app handler` for WuttaFarm.
    """

    display_format_datetime = "%a, %m/%d/%Y - %H:%M"

    default_auth_handler_spec = "wuttafarm.auth:WuttaFarmAuthHandler"
    default_install_handler_spec = "wuttafarm.install:WuttaFarmInstallHandler"

    def get_asset_handler(self):
        """
        Get the configured asset handler.

        :rtype: :class:`~wuttafarm.assets.AssetHandler`
        """
        if "asset" not in self.handlers:
            spec = self.config.get(
                f"{self.appname}.asset_handler",
                default="wuttafarm.assets:AssetHandler",
            )
            factory = self.load_object(spec)
            self.handlers["asset"] = factory(self.config)
        return self.handlers["asset"]

    def get_farmos_handler(self):
        """
        Get the configured farmOS integration handler.

        :rtype: :class:`~wuttafarm.farmos.FarmOSHandler`
        """
        if "farmos" not in self.handlers:
            spec = self.config.get(
                f"{self.appname}.farmos_handler",
                default="wuttafarm.farmos.handler:FarmOSHandler",
            )
            factory = self.load_object(spec)
            self.handlers["farmos"] = factory(self.config)
        return self.handlers["farmos"]

    def get_farmos_integration_mode(self):
        """
        Returns the integration mode for farmOS, i.e. to control the
        app's behavior regarding that.
        """
        enum = self.enum
        return self.config.get(
            f"{self.appname}.farmos_integration_mode",
            default=enum.FARMOS_INTEGRATION_MODE_WRAPPER,
        )

    def is_farmos_mirror(self):
        """
        Returns ``True`` if the app is configured in "mirror"
        integration mode with regard to farmOS.
        """
        enum = self.enum
        mode = self.get_farmos_integration_mode()
        return mode == enum.FARMOS_INTEGRATION_MODE_MIRROR

    def is_farmos_wrapper(self):
        """
        Returns ``True`` if the app is configured in "wrapper"
        integration mode with regard to farmOS.
        """
        enum = self.enum
        mode = self.get_farmos_integration_mode()
        return mode == enum.FARMOS_INTEGRATION_MODE_WRAPPER

    def is_standalone(self):
        """
        Returns ``True`` if the app is configured in "standalone" mode
        with regard to farmOS.
        """
        enum = self.enum
        mode = self.get_farmos_integration_mode()
        return mode == enum.FARMOS_INTEGRATION_MODE_NONE

    def get_farmos_url(self, *args, **kwargs):
        """
        Get a farmOS URL.  This is a convenience wrapper around
        :meth:`~wuttafarm.farmos.handler.FarmOSHandler.get_farmos_url()`.
        """
        handler = self.get_farmos_handler()
        return handler.get_farmos_url(*args, **kwargs)

    def get_farmos_client(self, *args, **kwargs):
        """
        Get a farmOS client.  This is a convenience wrapper around
        :meth:`~wuttafarm.farmos.handler.FarmOSHandler.get_farmos_client()`.
        """
        handler = self.get_farmos_handler()
        return handler.get_farmos_client(*args, **kwargs)

    def is_farmos_3x(self, *args, **kwargs):
        """
        Check if the farmOS version is 3.x.  This is a convenience
        wrapper around
        :meth:`~wuttafarm.farmos.handler.FarmOSHandler.is_farmos_3x()`.
        """
        handler = self.get_farmos_handler()
        return handler.is_farmos_3x(*args, **kwargs)

    def is_farmos_4x(self, *args, **kwargs):
        """
        Check if the farmOS version is 4.x.  This is a convenience
        wrapper around
        :meth:`~wuttafarm.farmos.handler.FarmOSHandler.is_farmos_4x()`.
        """
        handler = self.get_farmos_handler()
        return handler.is_farmos_4x(*args, **kwargs)

    def get_normalizer(self, farmos_client=None):
        """
        Get the configured farmOS integration handler.

        :rtype: :class:`~wuttafarm.farmos.FarmOSHandler`
        """
        spec = self.config.get(
            f"{self.appname}.normalizer_spec",
            default="wuttafarm.normal:Normalizer",
        )
        factory = self.load_object(spec)
        return factory(self.config, farmos_client)

    def get_quantity_types(self, session=None):
        """
        Returns a list of all known quantity types.
        """
        model = self.model
        with self.short_session(session=session) as sess:
            return (
                sess.query(model.QuantityType).order_by(model.QuantityType.name).all()
            )

    def get_measures(self, session=None):
        """
        Returns a list of all known measures.
        """
        model = self.model
        with self.short_session(session=session) as sess:
            return sess.query(model.Measure).order_by(model.Measure.ordinal).all()

    def get_units(self, session=None):
        """
        Returns a list of all known units.
        """
        model = self.model
        with self.short_session(session=session) as sess:
            return sess.query(model.Unit).order_by(model.Unit.name).all()

    def get_material_types(self, session=None):
        """
        Returns a list of all known material types.
        """
        model = self.model
        with self.short_session(session=session) as sess:
            return (
                sess.query(model.MaterialType).order_by(model.MaterialType.name).all()
            )

    def get_quantity_models(self):
        model = self.model
        return {
            "standard": model.StandardQuantity,
            "material": model.MaterialQuantity,
        }

    def get_true_quantity(self, quantity, require=True):
        model = self.model
        if not isinstance(quantity, model.Quantity):
            if require and not quantity:
                raise ValueError(f"quantity is not valid: {quantity}")
            return quantity

        session = self.get_session(quantity)
        models = self.get_quantity_models()
        if require and quantity.quantity_type_id not in models:
            raise ValueError(
                f"quantity has invalid quantity_type_id: {quantity.quantity_type_id}"
            )

        true_quantity = session.get(models[quantity.quantity_type_id], quantity.uuid)
        if require and not true_quantity:
            raise ValueError(f"quantity has no true/typed quantity record: {quantity}")

        return true_quantity

    def make_true_quantity(self, quantity_type_id, **kwargs):
        models = self.get_quantity_models()
        kwargs["quantity_type_id"] = quantity_type_id
        return models[quantity_type_id](**kwargs)

    def auto_sync_to_farmos(self, obj, model_name=None, client=None, require=True):
        """
        Export the given object to farmOS, using configured handler.

        This should ensure the given object is also *updated* with the
        farmOS UUID and Drupal ID, when new record is created in
        farmOS.

        :param obj: Any data object in WuttaFarm, e.g. AnimalAsset
           instance.

        :param client: Existing farmOS API client to use.  If not
           specified, a new one will be instantiated.

        :param require: If true, this will *require* the export
           handler to support objects of the given type.  If false,
           then nothing will happen / export is silently skipped when
           there is no such exporter.
        """
        handler = self.app.get_import_handler("export.to_farmos.from_wuttafarm")

        if not model_name:
            model_name = type(obj).__name__
        if model_name not in handler.importers:
            if require:
                raise ValueError(f"no exporter found for {model_name}")
            return

        # nb. begin txn to establish the API client
        handler.begin_target_transaction(client)
        importer = handler.get_importer(model_name, caches_target=False)
        normal = importer.normalize_source_object(obj)
        importer.process_data(source_data=[normal])

    def auto_sync_from_farmos(self, obj, model_name, client=None, require=True):
        """
        Import the given object from farmOS, using configured handler.

        :param obj: Any data record from farmOS.

        :param model_name': Model name for the importer to use,
           e.g. ``"AnimalAsset"``.

        :param client: Existing farmOS API client to use.  If not
           specified, a new one will be instantiated.

        :param require: If true, this will *require* the import
           handler to support objects of the given type.  If false,
           then nothing will happen / import is silently skipped when
           there is no such importer.
        """
        handler = self.app.get_import_handler("import.to_wuttafarm.from_farmos")

        if model_name not in handler.importers:
            if require:
                raise ValueError(f"no importer found for {model_name}")
            return

        # nb. begin txn to establish the API client
        handler.begin_source_transaction(client)
        with self.short_session(commit=True) as session:
            handler.target_session = session
            importer = handler.get_importer(model_name, caches_target=False)
            normal = importer.normalize_source_object(obj)
            importer.process_data(source_data=[normal])


class WuttaFarmAppProvider(base.AppProvider):
    """
    The :term:`app provider` for WuttaFarm.
    """

    email_modules = ["wuttafarm.emails"]
