# -*- 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/>.
#
################################################################################
"""
Base views for Logs
"""

from collections import OrderedDict

import colander
from webhelpers2.html import tags, HTML

from wuttaweb.forms.schema import WuttaDictEnum
from wuttaweb.db import Session
from wuttaweb.forms.widgets import WuttaDateTimeWidget

from wuttafarm.web.views import WuttaFarmMasterView
from wuttafarm.db.model import LogType, Log
from wuttafarm.web.forms.schema import AssetRefs, QuantityRefs, OwnerRefs
from wuttafarm.util import get_log_type_enum


class LogTypeView(WuttaFarmMasterView):
    """
    Master view for Log Types
    """

    model_class = LogType
    route_prefix = "log_types"
    url_prefix = "/log-types"

    grid_columns = [
        "name",
        "description",
    ]

    sort_defaults = "name"

    filter_defaults = {
        "name": {"active": True, "verb": "contains"},
    }

    form_fields = [
        "name",
        "description",
        "drupal_id",
        "farmos_uuid",
    ]

    def configure_grid(self, grid):
        g = grid
        super().configure_grid(g)

        # name
        g.set_link("name")

    def get_xref_buttons(self, log_type):
        buttons = super().get_xref_buttons(log_type)

        if log_type.farmos_uuid:
            buttons.append(
                self.make_button(
                    "View farmOS record",
                    primary=True,
                    url=self.request.route_url(
                        "farmos_log_types.view", uuid=log_type.farmos_uuid
                    ),
                    icon_left="eye",
                )
            )

        return buttons


class LogMasterView(WuttaFarmMasterView):
    """
    Base class for Asset master views
    """

    farmos_entity_type = "log"

    labels = {
        "message": "Log Name",
        "locations": "Location",
        "quantities": "Quantity",
    }

    grid_columns = [
        "status",
        "drupal_id",
        "timestamp",
        "message",
        "assets",
        "locations",
        "quantities",
        "is_group_assignment",
        "owners",
    ]

    sort_defaults = ("timestamp", "desc")

    filter_defaults = {
        "message": {"active": True, "verb": "contains"},
        "status": {"active": True, "verb": "not_equal", "value": "abandoned"},
    }

    form_fields = [
        "message",
        "timestamp",
        "assets",
        "groups",
        "locations",
        "quantities",
        "notes",
        "status",
        "log_type",
        "owners",
        "is_movement",
        "is_group_assignment",
        "quick",
        "drupal_id",
        "farmos_uuid",
    ]

    def get_query(self, session=None):
        """ """
        model = self.app.model
        model_class = self.get_model_class()
        session = session or self.Session()
        query = session.query(model_class)
        if model_class is not model.Log:
            query = query.join(model.Log)
        return query

    def configure_grid(self, grid):
        g = grid
        super().configure_grid(g)
        model = self.app.model
        enum = self.app.enum

        # status
        g.set_enum("status", enum.LOG_STATUS)
        g.set_sorter("status", model.Log.status)
        g.set_filter(
            "status",
            model.Log.status,
            verbs=["equal", "not_equal"],
            choices=enum.LOG_STATUS,
        )

        # drupal_id
        g.set_label("drupal_id", "ID", column_only=True)
        g.set_sorter("drupal_id", model.Log.drupal_id)
        g.set_filter("drupal_id", model.Log.drupal_id)

        # timestamp
        g.set_renderer("timestamp", "date")
        g.set_link("timestamp")
        g.set_sorter("timestamp", model.Log.timestamp)
        g.set_filter("timestamp", model.Log.timestamp)

        # message
        g.set_link("message")
        g.set_sorter("message", model.Log.message)
        g.set_filter("message", model.Log.message)

        # assets
        g.set_renderer("assets", self.render_assets_for_grid)

        # groups
        g.set_renderer("groups", self.render_assets_for_grid)

        # locations
        g.set_renderer("locations", self.render_assets_for_grid)

        # quantities
        g.set_renderer("quantities", self.render_quantities_for_grid)

        # is_group_assignment
        g.set_renderer("is_group_assignment", "boolean")
        g.set_sorter("is_group_assignment", model.Log.is_group_assignment)
        g.set_filter("is_group_assignment", model.Log.is_group_assignment)

        # owners
        g.set_label("owners", "Owner")
        g.set_renderer("owners", self.render_owners_for_grid)

    def render_assets_for_grid(self, log, field, value):
        assets = getattr(log, field)

        if self.farmos_style_grid_links:
            links = []
            for asset in assets:
                url = self.request.route_url(
                    f"{asset.asset_type}_assets.view", uuid=asset.uuid
                )
                links.append(tags.link_to(str(asset), url))
            return ", ".join(links)

        return ", ".join([str(a) for a in assets])

    def render_quantities_for_grid(self, log, field, value):
        quantities = getattr(log, field) or []
        items = []
        for qty in quantities:
            items.append(HTML.tag("li", c=qty.render_as_text(self.config)))
        return HTML.tag("ul", c=items)

    def render_owners_for_grid(self, log, field, value):

        if self.farmos_style_grid_links:
            links = []
            for user in log.owners:
                url = self.request.route_url("users.view", uuid=user.uuid)
                links.append(tags.link_to(user.username, url))
            return ", ".join(links)

        return ", ".join([user.username for user in log.owners])

    def grid_row_class(self, log, data, i):
        if log.status == "pending":
            return "has-background-warning"
        if log.status == "abandoned":
            return "has-background-danger"
        return None

    def configure_form(self, form):
        f = form
        super().configure_form(f)
        enum = self.app.enum
        session = self.Session()
        log = f.model_instance

        # timestamp
        # TODO: the widget should be automatic (assn proxy field)
        f.set_widget("timestamp", WuttaDateTimeWidget(self.request))
        if self.creating:
            f.set_default("timestamp", self.app.make_utc())

        # assets
        f.set_node("assets", AssetRefs(self.request))
        f.set_required("assets", False)
        if not self.creating:
            # nb. must explicity declare value for non-standard field
            f.set_default("assets", log.assets)

        # groups
        f.set_node("groups", AssetRefs(self.request, is_group=True))
        if not self.creating:
            # nb. must explicity declare value for non-standard field
            f.set_default("groups", log.groups)

        # locations
        f.set_node("locations", AssetRefs(self.request, is_location=True))
        if not self.creating:
            # nb. must explicity declare value for non-standard field
            f.set_default("locations", log.locations)

        # log_type
        if self.creating:
            f.remove("log_type")
        else:
            f.set_node(
                "log_type",
                WuttaDictEnum(
                    self.request, get_log_type_enum(self.config, session=session)
                ),
            )
            f.set_readonly("log_type")

        # quantities
        f.set_node("quantities", QuantityRefs(self.request))
        if not self.creating:
            # nb. must explicity declare value for non-standard field
            f.set_default(
                "quantities", [self.app.get_true_quantity(q) for q in log.quantities]
            )

        # notes
        f.set_widget("notes", "notes")

        # owners
        if self.creating or self.editing:
            f.remove("owners")  # TODO: need to support this
        else:
            f.set_node("owners", OwnerRefs(self.request))
            # nb. must explicity declare value for non-standard field
            f.set_default("owners", log.owners)

        # status
        f.set_node("status", WuttaDictEnum(self.request, enum.LOG_STATUS))

        # is_movement
        f.set_node("is_movement", colander.Boolean())

        # is_group_assignment
        f.set_node("is_group_assignment", colander.Boolean())

        # quick
        f.set_readonly("quick")  # TODO

    def objectify(self, form):
        log = super().objectify(form)
        data = form.validated

        if self.creating:

            # log_type
            log.log_type = self.get_farmos_log_type()

            # owner
            log.owners = [self.request.user]

        self.set_assets(log, data["assets"])
        self.set_locations(log, data["locations"])
        self.set_groups(log, data["groups"])
        self.set_quantities(log, data["quantities"])

        return log

    def set_assets(self, log, desired):
        model = self.app.model
        session = self.Session()
        current = [a.uuid for a in log.assets]

        for uuid in desired:
            if uuid not in current:
                asset = session.get(model.Asset, uuid)
                assert asset
                log.assets.append(asset)

        for uuid in current:
            if uuid not in desired:
                asset = session.get(model.Asset, uuid)
                assert asset
                log.assets.remove(asset)

    def set_locations(self, log, desired):
        model = self.app.model
        session = self.Session()
        current = [l.uuid for l in log.locations]

        for uuid in desired:
            if uuid not in current:
                location = session.get(model.Asset, uuid)
                assert location
                log.locations.append(location)

        for uuid in current:
            if uuid not in desired:
                location = session.get(model.Asset, uuid)
                assert location
                log.locations.remove(location)

    def set_groups(self, log, desired):
        model = self.app.model
        session = self.Session()
        current = [g.uuid for g in log.groups]

        for uuid in desired:
            if uuid not in current:
                group = session.get(model.Asset, uuid)
                assert group
                log.groups.append(group)

        for uuid in current:
            if uuid not in desired:
                group = session.get(model.Asset, uuid)
                assert group
                log.groups.remove(group)

    def set_quantities(self, log, desired):
        model = self.app.model
        session = self.Session()

        current = {
            qty.uuid.hex: self.app.get_true_quantity(qty) for qty in log.quantities
        }
        for new_qty in desired:
            units = session.get(model.Unit, new_qty["units"]["uuid"])
            assert units
            if new_qty["uuid"].startswith("new_"):
                qty = self.app.make_true_quantity(
                    new_qty["quantity_type"]["drupal_id"],
                    measure_id=new_qty["measure"],
                    value_numerator=int(new_qty["value"]),
                    value_denominator=1,
                    units=units,
                )
                # nb. must ensure "typed" quantity record persists!
                session.add(qty)
                # but must add "generic" quantity record to log
                log.quantities.append(qty.quantity)
            else:
                old_qty = current[new_qty["uuid"]]
                old_qty.measure_id = new_qty["measure"]
                old_qty.value_numerator = int(new_qty["value"])
                old_qty.value_denominator = 1
                old_qty.units = units
                if old_qty.quantity_type_id == "material":
                    self.set_material_types(old_qty, new_qty["material_types"])

        desired = [qty["uuid"] for qty in desired]
        for old_qty in list(log.quantities):
            # nb. "old_qty" may be newly-created, w/ no uuid yet
            # (this logic may break if session gets flushed early!)
            if old_qty.uuid and old_qty.uuid.hex not in desired:
                log.quantities.remove(old_qty)

    def set_material_types(self, quantity, desired):
        model = self.app.model
        session = self.Session()
        current = {mtype.uuid: mtype for mtype in quantity.material_types}

        for new_mtype in desired:
            mtype = session.get(model.MaterialType, new_mtype["uuid"])
            assert mtype
            if mtype.uuid not in current:
                quantity.material_types.append(mtype)

        desired = [mtype["uuid"] for mtype in desired]
        for old_mtype in current.values():
            if old_mtype.uuid.hex not in desired:
                quantity.material_types.remove(old_mtype)

    def auto_sync_to_farmos(self, client, log):
        model = self.app.model
        session = self.Session()

        # nb. ensure quantities have uuid keys
        session.flush()

        for qty in log.quantities:
            qty = self.app.get_true_quantity(qty)
            self.app.auto_sync_to_farmos(qty, client=client)

        self.app.auto_sync_to_farmos(log, client=client)

    def get_farmos_url(self, log):
        return self.app.get_farmos_url(f"/log/{log.drupal_id}")

    def get_farmos_log_type(self):
        return self.model_class.__wutta_hint__["farmos_log_type"]

    def get_xref_buttons(self, log):
        buttons = super().get_xref_buttons(log)

        if log.farmos_uuid:
            log_type = self.get_farmos_log_type()
            route = f"farmos_logs_{log_type}.view"
            buttons.append(
                self.make_button(
                    "View farmOS record",
                    primary=True,
                    url=self.request.route_url(route, uuid=log.farmos_uuid),
                    icon_left="eye",
                )
            )

        return buttons

    def get_version_joins(self):
        """
        We override this to declare the relationship between the
        view's data model (which is some type of log table) and the
        canonical ``Log`` model, so the revision history views include
        transactions which reference either version table.

        See also parent method,
        :meth:`~wuttaweb:wuttaweb.views.master.MasterView.get_version_joins()`
        """
        model = self.app.model
        return super().get_version_joins() + [
            model.Log,
            (model.LogAsset, "log_uuid", "uuid"),
        ]


class AllLogView(LogMasterView):
    """
    Master view for All Logs
    """

    model_class = Log
    route_prefix = "log"
    url_prefix = "/logs"

    farmos_refurl_path = "/logs"

    viewable = False
    creatable = False
    editable = False
    deletable = False
    model_is_versioned = False

    grid_columns = [
        "status",
        "drupal_id",
        "timestamp",
        "message",
        "log_type",
        "assets",
        "locations",
        "quantities",
        "groups",
        "is_group_assignment",
        "owners",
    ]

    def configure_grid(self, grid):
        g = grid
        super().configure_grid(g)
        session = self.Session()

        # log_type
        g.set_enum("log_type", get_log_type_enum(self.config, session=session))

        # view action links to final log record
        def log_url(log, i):
            return self.request.route_url(f"logs_{log.log_type}.view", uuid=log.uuid)

        g.add_action("view", icon="eye", url=log_url)


def defaults(config, **kwargs):
    base = globals()

    LogTypeView = kwargs.get("LogTypeView", base["LogTypeView"])
    LogTypeView.defaults(config)

    AllLogView = kwargs.get("AllLogView", base["AllLogView"])
    AllLogView.defaults(config)


def includeme(config):
    defaults(config)
