# -*- 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/>.
#
################################################################################
"""
Data import for farmOS -> WuttaFarm
"""

import datetime
import logging
from uuid import UUID

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

from wuttasync.importing import ImportHandler, ToWuttaHandler, Importer, ToWutta

from wuttafarm.db import model


log = logging.getLogger(__name__)


class FromFarmOSHandler(ImportHandler):
    """
    Base class for import handler using farmOS API as data source.
    """

    source_key = "farmos"
    generic_source_title = "farmOS"

    def begin_source_transaction(self):
        """
        Establish the farmOS API client.
        """
        token = self.get_farmos_oauth2_token()
        self.farmos_client = self.app.get_farmos_client(token=token)
        self.farmos_4x = self.app.is_farmos_4x(self.farmos_client)

    def get_farmos_oauth2_token(self):

        client_id = self.config.get(
            "farmos.oauth2.importing.client_id", default="wuttafarm"
        )
        client_secret = self.config.require("farmos.oauth2.importing.client_secret")
        scope = self.config.get("farmos.oauth2.importing.scope", default="farm_manager")

        client = BackendApplicationClient(client_id=client_id)
        oauth = OAuth2Session(client=client)

        return oauth.fetch_token(
            token_url=self.app.get_farmos_url("/oauth/token"),
            include_client_id=True,
            client_secret=client_secret,
            scope=scope,
        )

    def get_importer_kwargs(self, key, **kwargs):
        kwargs = super().get_importer_kwargs(key, **kwargs)
        kwargs["farmos_client"] = self.farmos_client
        kwargs["farmos_4x"] = self.farmos_4x
        return kwargs


class ToWuttaFarmHandler(ToWuttaHandler):
    """
    Base class for import handler targeting WuttaFarm
    """

    target_key = "wuttafarm"


class FromFarmOSToWuttaFarm(FromFarmOSHandler, ToWuttaFarmHandler):
    """
    Handler for farmOS → WuttaFarm import.
    """

    def define_importers(self):
        """ """
        importers = super().define_importers()
        importers["User"] = UserImporter
        importers["AssetType"] = AssetTypeImporter
        importers["LandType"] = LandTypeImporter
        importers["LandAsset"] = LandAssetImporter
        importers["StructureType"] = StructureTypeImporter
        importers["StructureAsset"] = StructureAssetImporter
        importers["AnimalType"] = AnimalTypeImporter
        importers["AnimalAsset"] = AnimalAssetImporter
        importers["GroupAsset"] = GroupAssetImporter
        importers["LogType"] = LogTypeImporter
        importers["ActivityLog"] = ActivityLogImporter
        return importers


class FromFarmOS(Importer):
    """
    Base class for importers using farmOS API as data source.
    """

    key = "farmos_uuid"

    def get_supported_fields(self):
        """
        Auto-remove the ``uuid`` field, since we use ``farmos_uuid``
        instead for the importer key.
        """
        fields = list(super().get_supported_fields())
        if "uuid" in fields:
            fields.remove("uuid")
        return fields

    def normalize_datetime(self, dt):
        """
        Convert a farmOS datetime value to naive UTC used by
        WuttaFarm.

        :param dt: Date/time string value "as-is" from the farmOS API.

        :returns: Equivalent naive UTC ``datetime``
        """
        dt = datetime.datetime.fromisoformat(dt)
        return self.app.make_utc(dt)


class ActivityLogImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Activity Logs
    """

    model_class = model.ActivityLog

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "message",
        "timestamp",
        "notes",
        "status",
    ]

    def get_source_objects(self):
        """ """
        logs = self.farmos_client.log.get("activity")
        return logs["data"]

    def normalize_source_object(self, log):
        """ """

        if notes := log["attributes"]["notes"]:
            notes = notes["value"]

        return {
            "farmos_uuid": UUID(log["id"]),
            "drupal_id": log["attributes"]["drupal_internal__id"],
            "message": log["attributes"]["name"],
            "timestamp": self.normalize_datetime(log["attributes"]["timestamp"]),
            "notes": notes,
            "status": log["attributes"]["status"],
        }


class AssetImporterBase(FromFarmOS, ToWutta):
    """
    Base class for farmOS API → WuttaFarm asset importers
    """

    def get_simple_fields(self):
        """ """
        fields = list(super().get_simple_fields())
        # nb. must explicitly declare proxy fields
        fields.extend(
            [
                "farmos_uuid",
                "drupal_id",
                "asset_type",
                "asset_name",
                "is_location",
                "is_fixed",
                "notes",
                "archived",
                "image_url",
                "thumbnail_url",
            ]
        )
        return fields

    def get_supported_fields(self):
        """ """
        fields = list(super().get_supported_fields())
        fields.extend(
            [
                "parents",
            ]
        )
        return fields

    def normalize_source_data(self, **kwargs):
        """ """
        data = super().normalize_source_data(**kwargs)

        if "parents" in self.fields:
            # nb. make sure parent-less (root) assets come first, so they
            # exist when child assets need to reference them
            data.sort(key=lambda l: len(l["parents"]))

        return data

    def normalize_asset(self, asset):
        """ """
        image_url = None
        thumbnail_url = None
        if relationships := asset.get("relationships"):

            if image := relationships.get("image"):
                if image["data"]:
                    image = self.farmos_client.resource.get_id(
                        "file", "file", image["data"][0]["id"]
                    )
                    if image_style := image["data"]["attributes"].get(
                        "image_style_uri"
                    ):
                        image_url = image_style["large"]
                        thumbnail_url = image_style["thumbnail"]

        if notes := asset["attributes"]["notes"]:
            notes = notes["value"]

        if self.farmos_4x:
            archived = asset["attributes"]["archived"]
        else:
            archived = asset["attributes"]["status"] == "archived"

        parents = None
        if "parents" in self.fields:
            parents = []
            for parent in asset["relationships"]["parent"]["data"]:
                parents.append((self.get_asset_type(parent), UUID(parent["id"])))

        return {
            "farmos_uuid": UUID(asset["id"]),
            "drupal_id": asset["attributes"]["drupal_internal__id"],
            "asset_name": asset["attributes"]["name"],
            "is_location": asset["attributes"]["is_location"],
            "is_fixed": asset["attributes"]["is_fixed"],
            "archived": archived,
            "notes": notes,
            "image_url": image_url,
            "thumbnail_url": thumbnail_url,
            "parents": parents,
        }

    def get_asset_type(self, asset):
        return asset["type"].split("--")[1]

    def normalize_target_object(self, asset):
        data = super().normalize_target_object(asset)

        if "parents" in self.fields:
            data["parents"] = [
                (p.parent.asset_type, p.parent.farmos_uuid)
                for p in asset.asset._parents
            ]

        return data

    def update_target_object(self, asset, source_data, target_data=None):
        model = self.app.model
        asset = super().update_target_object(asset, source_data, target_data)

        if "parents" in self.fields:
            if not target_data or target_data["parents"] != source_data["parents"]:

                for key in source_data["parents"]:
                    asset_type, farmos_uuid = key
                    if not target_data or key not in target_data["parents"]:
                        self.target_session.flush()
                        parent = (
                            self.target_session.query(model.Asset)
                            .filter(model.Asset.asset_type == asset_type)
                            .filter(model.Asset.farmos_uuid == farmos_uuid)
                            .one()
                        )
                        asset.asset._parents.append(model.AssetParent(parent=parent))

                if target_data:
                    for key in target_data["parents"]:
                        asset_type, farmos_uuid = key
                        if key not in source_data["parents"]:
                            parent = (
                                self.target_session.query(model.Asset)
                                .filter(model.Asset.asset_type == asset_type)
                                .filter(model.Asset.farmos_uuid == farmos_uuid)
                                .one()
                            )
                            parent = (
                                self.target_session.query(model.AssetParent)
                                .filter(model.AssetParent.asset == asset)
                                .filter(model.AssetParent.parent == parent)
                                .one()
                            )
                            self.target_session.delete(parent)

        return asset


class AnimalAssetImporter(AssetImporterBase):
    """
    farmOS API → WuttaFarm importer for Animals
    """

    model_class = model.AnimalAsset

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "asset_type",
        "asset_name",
        "animal_type_uuid",
        "sex",
        "is_sterile",
        "birthdate",
        "notes",
        "archived",
        "image_url",
        "thumbnail_url",
    ]

    def setup(self):
        super().setup()
        model = self.app.model

        self.animal_types_by_farmos_uuid = {}
        for animal_type in self.target_session.query(model.AnimalType):
            if animal_type.farmos_uuid:
                self.animal_types_by_farmos_uuid[animal_type.farmos_uuid] = animal_type

    def get_source_objects(self):
        """ """
        animals = self.farmos_client.asset.get("animal")
        return animals["data"]

    def normalize_source_object(self, animal):
        """ """
        animal_type_uuid = None
        if relationships := animal.get("relationships"):

            if animal_type := relationships.get("animal_type"):
                if animal_type["data"]:
                    if wf_animal_type := self.animal_types_by_farmos_uuid.get(
                        UUID(animal_type["data"]["id"])
                    ):
                        animal_type_uuid = wf_animal_type.uuid
                    else:
                        log.warning(
                            "animal type not found: %s", animal_type["data"]["id"]
                        )

        if not animal_type_uuid:
            log.warning("missing/invalid animal_type for farmOS Animal: %s", animal)
            return None

        birthdate = animal["attributes"]["birthdate"]
        if birthdate:
            birthdate = datetime.datetime.fromisoformat(birthdate)
            birthdate = self.app.localtime(birthdate)
            birthdate = self.app.make_utc(birthdate)

        if self.farmos_4x:
            sterile = animal["attributes"]["is_sterile"]
        else:
            sterile = animal["attributes"]["is_castrated"]

        data = self.normalize_asset(animal)
        data.update(
            {
                "asset_type": "animal",
                "animal_type_uuid": animal_type_uuid,
                "sex": animal["attributes"]["sex"],
                "is_sterile": sterile,
                "birthdate": birthdate,
            }
        )
        return data


class AnimalTypeImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Animal Types
    """

    model_class = model.AnimalType

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "name",
        "description",
        "changed",
    ]

    def get_source_objects(self):
        """ """
        animal_types = self.farmos_client.resource.get("taxonomy_term", "animal_type")
        return animal_types["data"]

    def normalize_source_object(self, animal_type):
        """ """
        return {
            "farmos_uuid": UUID(animal_type["id"]),
            "drupal_id": animal_type["attributes"]["drupal_internal__tid"],
            "name": animal_type["attributes"]["name"],
            "description": animal_type["attributes"]["description"],
            "changed": self.normalize_datetime(animal_type["attributes"]["changed"]),
        }


class AssetTypeImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Asset Types
    """

    model_class = model.AssetType

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

    def get_source_objects(self):
        """ """
        asset_types = self.farmos_client.resource.get("asset_type")
        return asset_types["data"]

    def normalize_source_object(self, asset_type):
        """ """
        return {
            "farmos_uuid": UUID(asset_type["id"]),
            "drupal_id": asset_type["attributes"]["drupal_internal__id"],
            "name": asset_type["attributes"]["label"],
            "description": asset_type["attributes"]["description"],
        }


class GroupAssetImporter(AssetImporterBase):
    """
    farmOS API → WuttaFarm importer for Group Assets
    """

    model_class = model.GroupAsset

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "asset_type",
        "asset_name",
        "is_location",
        "is_fixed",
        "notes",
        "archived",
        "image_url",
        "thumbnail_url",
        "parents",
    ]

    def get_source_objects(self):
        """ """
        groups = self.farmos_client.asset.get("group")
        return groups["data"]

    def normalize_source_object(self, group):
        """ """
        data = self.normalize_asset(group)
        data.update(
            {
                "asset_type": "group",
            }
        )
        return data


class LandAssetImporter(AssetImporterBase):
    """
    farmOS API → WuttaFarm importer for Land Assets
    """

    model_class = model.LandAsset

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "asset_type",
        "asset_name",
        "land_type_uuid",
        "is_location",
        "is_fixed",
        "notes",
        "archived",
        "parents",
    ]

    def setup(self):
        """ """
        super().setup()
        model = self.app.model

        self.land_types_by_id = {}
        for land_type in self.target_session.query(model.LandType):
            self.land_types_by_id[land_type.drupal_id] = land_type

    def get_source_objects(self):
        """ """
        land_assets = self.farmos_client.asset.get("land")
        return land_assets["data"]

    def normalize_source_object(self, land):
        """ """
        land_type_id = land["attributes"]["land_type"]
        land_type = self.land_types_by_id.get(land_type_id)
        if not land_type:
            log.warning(
                "invalid land_type '%s' for farmOS Land Asset: %s", land_type_id, land
            )
            return None

        data = self.normalize_asset(land)
        data.update(
            {
                "asset_type": "land",
                "land_type_uuid": land_type.uuid,
            }
        )
        return data


class LandTypeImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Land Types
    """

    model_class = model.LandType

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "name",
    ]

    def get_source_objects(self):
        """ """
        land_types = self.farmos_client.resource.get("land_type")
        return land_types["data"]

    def normalize_source_object(self, land_type):
        """ """
        return {
            "farmos_uuid": UUID(land_type["id"]),
            "drupal_id": land_type["attributes"]["drupal_internal__id"],
            "name": land_type["attributes"]["label"],
        }


class LogTypeImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Log Types
    """

    model_class = model.LogType

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

    def get_source_objects(self):
        """ """
        log_types = self.farmos_client.resource.get("log_type")
        return log_types["data"]

    def normalize_source_object(self, log_type):
        """ """
        return {
            "farmos_uuid": UUID(log_type["id"]),
            "drupal_id": log_type["attributes"]["drupal_internal__id"],
            "name": log_type["attributes"]["label"],
            "description": log_type["attributes"]["description"],
        }


class StructureAssetImporter(AssetImporterBase):
    """
    farmOS API → WuttaFarm importer for Structure Assets
    """

    model_class = model.StructureAsset

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "asset_type",
        "asset_name",
        "structure_type_uuid",
        "is_location",
        "is_fixed",
        "notes",
        "archived",
        "image_url",
        "thumbnail_url",
        "parents",
    ]

    def setup(self):
        super().setup()
        model = self.app.model

        self.structure_types_by_id = {}
        for structure_type in self.target_session.query(model.StructureType):
            self.structure_types_by_id[structure_type.drupal_id] = structure_type

    def get_source_objects(self):
        """ """
        structures = self.farmos_client.asset.get("structure")
        return structures["data"]

    def normalize_source_object(self, structure):
        """ """
        structure_type_id = structure["attributes"]["structure_type"]
        structure_type = self.structure_types_by_id.get(structure_type_id)
        if not structure_type:
            log.warning(
                "invalid structure_type '%s' for farmOS Structure Asset: %s",
                structure_type_id,
                structure,
            )
            return None

        data = self.normalize_asset(structure)
        data.update(
            {
                "asset_type": "structure",
                "structure_type_uuid": structure_type.uuid,
            }
        )
        return data


class StructureTypeImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Structure Types
    """

    model_class = model.StructureType

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "name",
    ]

    def get_source_objects(self):
        """ """
        structure_types = self.farmos_client.resource.get("structure_type")
        return structure_types["data"]

    def normalize_source_object(self, structure_type):
        """ """
        return {
            "farmos_uuid": UUID(structure_type["id"]),
            "drupal_id": structure_type["attributes"]["drupal_internal__id"],
            "name": structure_type["attributes"]["label"],
        }


class UserImporter(FromFarmOS, ToWutta):
    """
    farmOS API → WuttaFarm importer for Users
    """

    model_class = model.User

    supported_fields = [
        "farmos_uuid",
        "drupal_id",
        "username",
    ]

    def get_simple_fields(self):
        """ """
        fields = list(super().get_simple_fields())
        # nb. must explicitly declare extension fields
        fields.extend(
            [
                "farmos_uuid",
                "drupal_id",
            ]
        )
        return fields

    def get_source_objects(self):
        """ """
        users = self.farmos_client.resource.get("user")
        return users["data"]

    def normalize_source_object(self, user):
        """ """

        # nb. skip Anonymous user which does not have drupal id
        drupal_id = user["attributes"].get("drupal_internal__uid")
        if not drupal_id:
            return None

        return {
            "farmos_uuid": UUID(user["id"]),
            "drupal_id": drupal_id,
            "username": user["attributes"]["name"],
        }

    def can_delete_object(self, user, data=None):
        """
        Prevent delete for users which do not exist in farmOS.
        """
        if not user.farmos_uuid:
            return False
        return True
