# -*- 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/>.
#
################################################################################
"""
WuttaFarm CLI
"""

import logging
import time

import typer
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

from wuttafarm.cli import wuttafarm_typer


log = logging.getLogger(__name__)


class ChangeProcessor:

    def __init__(self, config):
        self.config = config
        self.app = config.get_app()

    def process_change(self, change):
        if change.deleted:
            self.delete_record(change)
        else:
            self.import_record(change)

    def import_record(self, change):
        token = self.get_farmos_oauth2_token()
        client = self.app.get_farmos_client(token=token)

        full_type = f"{change.entity_type}--{change.bundle}"
        record = client.resource.get_id(
            change.entity_type, change.bundle, change.farmos_uuid
        )

        importer_map = self.get_importer_map()
        model_name = importer_map[full_type]

        self.app.auto_sync_from_farmos(record["data"], model_name, client=client)

    def delete_record(self, change):
        model = self.app.model
        handler = self.app.get_import_handler("import.to_wuttafarm.from_farmos")

        importer_map = self.get_importer_map()
        full_type = f"{change.entity_type}--{change.bundle}"
        model_name = importer_map[full_type]

        token = self.get_farmos_oauth2_token()
        client = self.app.get_farmos_client(token=token)

        # nb. begin txn to establish the API client
        handler.begin_source_transaction(client)
        with self.app.short_session(commit=True) as session:
            handler.target_session = session
            importer = handler.get_importer(model_name, caches_target=False)

            # try to attribute change to 'farmos' user
            if user := session.query(model.User).filter_by(username="farmos").first():
                session.info["continuum_user_id"] = user.uuid

            # only support importers with farmos_uuid as key
            # (pretty sure that covers us..can revise if needed)
            if importer.get_keys() != ["farmos_uuid"]:
                log.warning(
                    "unsupported keys for %s importer: %s",
                    model_name,
                    importer.get_keys(),
                )
                return

            # delete corresponding record from our app
            obj = importer.get_target_object((change.uuid,))
            if obj:
                importer.delete_target_object(obj)

    # TODO: this should live elsewhere
    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,
        )

    # TODO: this should live elsewhere
    def get_importer_map(self):
        return {
            "asset--animal": "AnimalAsset",
            "asset--equipment": "EquipmentAsset",
            "asset--group": "GroupAsset",
            "asset--land": "LandAsset",
            "asset--plant": "PlantAsset",
            "asset--structure": "StructureAsset",
            "asset--water": "WaterAsset",
            "log--activity": "ActivityLog",
            "log--harvest": "HarvestLog",
            "log--medical": "MedicalLog",
            "log--observation": "ObservationLog",
            "log--seeding": "SeedingLog",
            "quantity--material": "MaterialQuantity",
            "quantity--standard": "StandardQuantity",
            "taxonomy_term--animal_type": "AnimalType",
            "taxonomy_term--equipment_type": "EquipmentType",
            "taxonomy_term--plant_type": "PlantType",
            "taxonomy_term--season": "Season",
            "taxonomy_term--material_type": "MaterialType",
            "taxonomy_term--unit": "Unit",
        }


@wuttafarm_typer.command()
def process_webhooks(
    ctx: typer.Context,
):
    """
    Process incoming webhook requests from farmOS.
    """
    config = ctx.parent.wutta_config
    app = config.get_app()
    model = app.model
    processor = ChangeProcessor(config)

    while True:
        with app.short_session(commit=True) as session:

            query = session.query(model.WebhookChange).order_by(
                model.WebhookChange.received
            )

            # nb. fetch (at most) 2 changes instead of just 1;
            # this will control time delay behavior below
            if changes := query[:2]:

                # process first change
                change = changes[0]
                log.info("processing webhook change: %s", change)
                processor.process_change(change)
                session.delete(change)

                # minimal time delay if 2nd change exists
                if len(changes) == 2:
                    time.sleep(0.1)
                    continue

        # nothing in queue, so wait 1 sec before checking again
        time.sleep(1)
