# -*- 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 form widgets for WuttaFarm
"""

import json

import colander
from deform.widget import Widget, SelectWidget, sequence_types, _normalize_choices
from webhelpers2.html import HTML, tags

from wuttaweb.forms.widgets import WuttaCheckboxChoiceWidget, ObjectRefWidget
from wuttaweb.db import Session

from wuttafarm.web.util import render_quantity_objects
from wuttafarm.db.model import EquipmentType


class ImageWidget(Widget):
    """
    Widget to display an image URL for a record.
    """

    def __init__(self, alt_text, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.alt_text = alt_text

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            return tags.image(cstruct, self.alt_text, **kw)

        return super().serialize(field, cstruct, **kw)


class LogQuickWidget(Widget):
    """
    Widget to display an image URL for a record.
    """

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            items = []
            for quick in json.loads(cstruct):
                items.append(HTML.tag("li", c=quick))
            return HTML.tag("ul", c=items)

        return super().serialize(field, cstruct, **kw)


class FarmOSRefWidget(SelectWidget):
    """
    Generic widget to display "any reference field" - as a link to
    view the farmOS record it references.  Only used by the farmOS
    direct API views.
    """

    def __init__(self, request, route_prefix, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.route_prefix = route_prefix

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            try:
                obj = json.loads(cstruct)
            except json.JSONDecodeError:
                name = dict(self.values)[cstruct]
                obj = {"uuid": cstruct, "name": name}

            return tags.link_to(
                obj["name"],
                self.request.route_url(f"{self.route_prefix}.view", uuid=obj["uuid"]),
            )

        return super().serialize(field, cstruct, **kw)


class FarmOSRefsWidget(Widget):

    def __init__(self, request, route_prefix, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.route_prefix = route_prefix

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            links = []
            for obj in cstruct:
                url = self.request.route_url(
                    f"{self.route_prefix}.view", uuid=obj["uuid"]
                )
                links.append(HTML.tag("li", c=tags.link_to(obj["name"], url)))

            return HTML.tag("ul", c=links)

        return super().serialize(field, cstruct, **kw)


class FarmOSAssetRefsWidget(Widget):
    """
    Widget to display a "Assets" field for an asset.
    """

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            assets = []
            for asset in json.loads(cstruct):
                url = self.request.route_url(
                    f"farmos_{asset['asset_type']}_assets.view", uuid=asset["uuid"]
                )
                assets.append(HTML.tag("li", c=tags.link_to(asset["name"], url)))

            return HTML.tag("ul", c=assets)

        return super().serialize(field, cstruct, **kw)


class FarmOSLocationRefsWidget(Widget):
    """
    Widget to display a "Locations" field for an asset.
    """

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            locations = []
            for location in json.loads(cstruct):
                asset_type = location["type"].split("--")[1]
                url = self.request.route_url(
                    f"farmos_{asset_type}_assets.view", uuid=location["uuid"]
                )
                locations.append(HTML.tag("li", c=tags.link_to(location["name"], url)))

            return HTML.tag("ul", c=locations)

        return super().serialize(field, cstruct, **kw)


class FarmOSQuantityRefsWidget(Widget):
    """
    Widget to display a "Quantities" field for a log.
    """

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            quantities = json.loads(cstruct)
            return render_quantity_objects(quantities)

        return super().serialize(field, cstruct, **kw)


class FarmOSUnitRefWidget(Widget):
    """
    Widget to display a "Units" field for a quantity.
    """

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            unit = json.loads(cstruct)
            return unit["name"]

        return super().serialize(field, cstruct, **kw)


class FarmOSTaxonomyTermsWidget(Widget):
    """
    Widget to display a field which can reference multiple taxonomy
    terms.
    """

    def __init__(self, request, route_prefix, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.route_prefix = route_prefix

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            links = []
            for term in cstruct:
                link = tags.link_to(
                    term["name"],
                    self.request.route_url(
                        f"{self.route_prefix}.view", uuid=term["uuid"]
                    ),
                )
                links.append(HTML.tag("li", c=link))
            return HTML.tag("ul", c=links)

        return super().serialize(field, cstruct, **kw)


class FarmOSPlantTypesWidget(Widget):
    """
    Widget to display a farmOS "plant types" field.
    """

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            links = []
            for plant_type in json.loads(cstruct):
                link = tags.link_to(
                    plant_type["name"],
                    self.request.route_url(
                        "farmos_plant_types.view", uuid=plant_type["uuid"]
                    ),
                )
                links.append(HTML.tag("li", c=link))
            return HTML.tag("ul", c=links)

        return super().serialize(field, cstruct, **kw)


class TaxonomyTermRefsWidget(Widget):
    """
    Generic (incomplete) widget for fields which can reference
    multiple taxonomy terms.

    This widget can handle typical read-only scenarios but the
    editable mode is not implemented.
    """

    route_prefix = None

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.config = self.request.wutta_config
        self.app = self.config.get_app()

    @classmethod
    def get_route_prefix(cls):
        return cls.route_prefix

    @classmethod
    def get_permission_prefix(cls):
        return cls.route_prefix

    def serialize(self, field, cstruct, **kw):
        """ """
        if not cstruct:
            cstruct = []

        if readonly := kw.get("readonly", self.readonly):
            items = []
            route_prefix = self.get_route_prefix()
            for term in cstruct:
                url = self.request.route_url(f"{route_prefix}.view", uuid=term["uuid"])
                link = tags.link_to(term["name"], url)
                items.append(HTML.tag("li", c=link))
            return HTML.tag("ul", c=items)

        tmpl_values = self.get_template_values(field, cstruct, kw)
        return field.renderer(self.template, **tmpl_values)

    def get_template_values(self, field, cstruct, kw):
        values = super().get_template_values(field, cstruct, kw)
        model = self.app.model
        session = Session()

        terms = []
        query = session.query(self.model_class).order_by(self.model_class.name)
        for term in query:
            terms.append(
                {
                    "uuid": str(term.uuid),
                    "name": term.name,
                }
            )
        values["terms"] = terms

        permission_prefix = self.get_permission_prefix()
        if self.request.has_perm(f"{permission_prefix}.create"):
            values["can_create"] = True

        return values

    def deserialize(self, field, pstruct):
        """ """
        if not pstruct:
            return colander.null

        return json.loads(pstruct)


class EquipmentTypeRefsWidget(TaxonomyTermRefsWidget):
    """
    Widget for Equipment Types field.
    """

    model_class = EquipmentType
    route_prefix = "equipment_types"
    template = "equipmenttyperefs"


class PlantTypeRefsWidget(Widget):
    """
    Widget for Plant Types field (on a Plant Asset).
    """

    template = "planttyperefs"
    values = ()

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.config = self.request.wutta_config
        self.app = self.config.get_app()

    def serialize(self, field, cstruct, **kw):
        """ """
        model = self.app.model
        session = Session()

        if cstruct in (colander.null, None):
            cstruct = ()

        if readonly := kw.get("readonly", self.readonly):
            items = []

            plant_types = (
                session.query(model.PlantType)
                .filter(model.PlantType.uuid.in_(cstruct))
                .order_by(model.PlantType.name)
                .all()
            )

            for plant_type in plant_types:
                items.append(
                    HTML.tag(
                        "li",
                        c=tags.link_to(
                            str(plant_type),
                            self.request.route_url(
                                "plant_types.view", uuid=plant_type.uuid
                            ),
                        ),
                    )
                )

            return HTML.tag("ul", c=items)

        values = kw.get("values", self.values)
        if not isinstance(values, sequence_types):
            raise TypeError("Values must be a sequence type (list, tuple, or range).")

        kw["values"] = _normalize_choices(values)
        tmpl_values = self.get_template_values(field, cstruct, kw)
        return field.renderer(self.template, **tmpl_values)

    def get_template_values(self, field, cstruct, kw):
        """ """
        values = super().get_template_values(field, cstruct, kw)

        values["js_values"] = json.dumps(values["values"])

        if self.request.has_perm("plant_types.create"):
            values["can_create"] = True

        return values

    def deserialize(self, field, pstruct):
        """ """
        if not pstruct:
            return colander.null

        return set(pstruct.split(","))


class MaterialTypeRefsWidget(Widget):
    """
    Widget for Material Types field (on a Material Asset).
    """

    template = "materialtyperefs"
    values = ()

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.config = self.request.wutta_config
        self.app = self.config.get_app()

    def serialize(self, field, cstruct, **kw):
        """ """
        model = self.app.model
        session = Session()

        if not cstruct:
            cstruct = []

        if readonly := kw.get("readonly", self.readonly):
            items = []
            for mtype in cstruct:
                items.append(
                    HTML.tag(
                        "li",
                        c=tags.link_to(
                            mtype["name"],
                            self.request.route_url(
                                "material_types.view", uuid=mtype["uuid"]
                            ),
                        ),
                    )
                )
            return HTML.tag("ul", c=items)

        tmpl_values = self.get_template_values(field, cstruct, kw)
        return field.renderer(self.template, **tmpl_values)

    def get_template_values(self, field, cstruct, kw):
        """ """
        values = super().get_template_values(field, cstruct, kw)
        session = Session()

        material_types = []
        for mtype in self.app.get_material_types(session):
            material_types.append(
                {
                    "uuid": mtype.uuid.hex,
                    "name": mtype.name,
                }
            )
        values["material_types"] = json.dumps(material_types)

        return values

    def deserialize(self, field, pstruct):
        """ """
        if not pstruct:
            return []

        return json.loads(pstruct)


class SeasonRefsWidget(Widget):
    """
    Widget for Seasons field (on a Plant Asset).
    """

    template = "seasonrefs"
    values = ()

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.config = self.request.wutta_config
        self.app = self.config.get_app()

    def serialize(self, field, cstruct, **kw):
        """ """
        model = self.app.model
        session = Session()

        if cstruct in (colander.null, None):
            cstruct = ()

        if readonly := kw.get("readonly", self.readonly):
            items = []

            seasons = (
                session.query(model.Season)
                .filter(model.Season.uuid.in_(cstruct))
                .order_by(model.Season.name)
                .all()
            )

            for season in seasons:
                items.append(
                    HTML.tag(
                        "li",
                        c=tags.link_to(
                            str(season),
                            self.request.route_url("seasons.view", uuid=season.uuid),
                        ),
                    )
                )

            return HTML.tag("ul", c=items)

        values = kw.get("values", self.values)
        if not isinstance(values, sequence_types):
            raise TypeError("Values must be a sequence type (list, tuple, or range).")

        kw["values"] = _normalize_choices(values)
        tmpl_values = self.get_template_values(field, cstruct, kw)
        return field.renderer(self.template, **tmpl_values)

    def get_template_values(self, field, cstruct, kw):
        """ """
        values = super().get_template_values(field, cstruct, kw)

        values["js_values"] = json.dumps(values["values"])

        if self.request.has_perm("seasons.create"):
            values["can_create"] = True

        return values

    def deserialize(self, field, pstruct):
        """ """
        if not pstruct:
            return set()

        return set(pstruct.split(","))


class StructureWidget(Widget):
    """
    Widget to display a "structure" field.
    """

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            structure = json.loads(cstruct)
            return tags.link_to(
                structure["name"],
                self.request.route_url(
                    "farmos_structure_assets.view", uuid=structure["uuid"]
                ),
            )

        return super().serialize(field, cstruct, **kw)


class UsersWidget(Widget):
    """
    Widget to display the list of owners for an asset etc.
    """

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def serialize(self, field, cstruct, **kw):
        """ """
        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if cstruct in (colander.null, None):
                return HTML.tag("span")

            items = []
            for user in json.loads(cstruct):
                link = tags.link_to(
                    user["display_name"],
                    self.request.route_url("farmos_users.view", uuid=user["uuid"]),
                )
                items.append(HTML.tag("li", c=link))

            return HTML.tag("ul", c=items)

        return super().serialize(field, cstruct, **kw)


##############################
# native data widgets
##############################


class AssetRefsWidget(Widget):
    """
    Widget for Assets field (of various kinds).
    """

    template = "assetrefs"
    values = ()

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.config = self.request.wutta_config
        self.app = self.config.get_app()

    def serialize(self, field, cstruct, **kw):
        """ """
        model = self.app.model
        session = Session()

        readonly = kw.get("readonly", self.readonly)
        if readonly:
            assets = []
            for uuid in cstruct or []:
                asset = session.get(model.Asset, uuid)
                assets.append(
                    HTML.tag(
                        "li",
                        c=tags.link_to(
                            str(asset),
                            self.request.route_url(
                                f"{asset.asset_type}_assets.view", uuid=asset.uuid
                            ),
                        ),
                    )
                )
            return HTML.tag("ul", c=assets)

        values = kw.get("values", self.values)
        if not isinstance(values, sequence_types):
            raise TypeError("Values must be a sequence type (list, tuple, or range).")

        kw["values"] = _normalize_choices(values)
        tmpl_values = self.get_template_values(field, cstruct, kw)
        return field.renderer(self.template, **tmpl_values)

    def get_template_values(self, field, cstruct, kw):
        """ """
        values = super().get_template_values(field, cstruct, kw)

        values["js_values"] = json.dumps(values["values"])

        return values

    def deserialize(self, field, pstruct):
        """ """
        if not pstruct:
            return set()

        return set(pstruct.split(","))


class QuantityRefsWidget(Widget):
    """
    Widget for Quantities field (on a Log record)
    """

    template = "quantityrefs"

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request
        self.config = self.request.wutta_config
        self.app = self.config.get_app()

    def serialize(self, field, cstruct, **kw):
        """ """
        model = self.app.model
        session = Session()

        readonly = kw.get("readonly", self.readonly)
        if readonly:
            if not cstruct:
                return ""

            quantities = []
            for qty in cstruct:
                url = self.request.route_url(
                    f"quantities_{qty['quantity_type']['drupal_id']}.view",
                    uuid=qty["uuid"],
                )
                quantities.append(HTML.tag("li", c=tags.link_to(qty["as_text"], url)))

            return HTML.tag("ul", c=quantities)

        tmpl_values = self.get_template_values(field, cstruct, kw)
        return field.renderer(self.template, **tmpl_values)

    def get_template_values(self, field, cstruct, kw):
        model = self.app.model
        session = Session()
        values = super().get_template_values(field, cstruct, kw)

        qtypes = []
        for qtype in self.app.get_quantity_types(session):
            qtypes.append(
                {
                    "uuid": qtype.uuid.hex,
                    "drupal_id": qtype.drupal_id,
                    "name": qtype.name,
                }
            )
        values["quantity_types"] = qtypes

        material_types = []
        for mtype in self.app.get_material_types(session):
            material_types.append(
                {
                    "uuid": mtype.uuid.hex,
                    "name": mtype.name,
                }
            )
        values["material_types"] = material_types

        measures = []
        for measure in self.app.get_measures(session):
            measures.append(
                {
                    "uuid": measure.uuid.hex,
                    "drupal_id": measure.drupal_id,
                    "name": measure.name,
                }
            )
        values["measures"] = measures

        units = []
        for unit in self.app.get_units(session):
            units.append(
                {
                    "uuid": unit.uuid.hex,
                    "drupal_id": unit.drupal_id,
                    "name": unit.name,
                }
            )
        values["units"] = units

        return values

    def deserialize(self, field, pstruct):
        """ """
        if not pstruct:
            return set()

        return json.loads(pstruct)


class OwnerRefsWidget(WuttaCheckboxChoiceWidget):
    """
    Widget for Owners field (on an Asset or Log record)
    """

    def serialize(self, field, cstruct, **kw):
        """ """
        model = self.app.model
        session = Session()

        readonly = kw.get("readonly", self.readonly)
        if readonly:
            owners = [session.get(model.User, uuid) for uuid in cstruct or []]
            owners = [user for user in owners if user]
            owners.sort(key=lambda user: user.username)
            links = []
            for user in owners:
                links.append(
                    HTML.tag(
                        "li",
                        c=tags.link_to(
                            user.username,
                            self.request.route_url("users.view", uuid=user.uuid),
                        ),
                    )
                )
            return HTML.tag("ul", c=links)

        return super().serialize(field, cstruct, **kw)


class AnimalTypeRefWidget(ObjectRefWidget):
    """
    Custom widget which uses the ``<animal-type-picker>`` component.
    """

    template = "animaltyperef"

    def get_template_values(self, field, cstruct, kw):
        """ """
        values = super().get_template_values(field, cstruct, kw)

        values["js_values"] = json.dumps(values["values"])

        if self.request.has_perm("animal_types.create"):
            values["can_create"] = True

        return values
