"""Generic delta applier for free action consequences."""

from __future__ import annotations

from app.infra.logging import get_logger
from app.sim.state_delta_models import StateDelta
from app.sim.world import WorldState
from app.store.repositories import AgentEconomicStateRepository, EconomicEffectLogRepository

logger = get_logger(__name__)


class DeltaApplier:
    """Applies StateDelta to the database.

    This class validates and applies state changes generated by the
    ConsequenceGenerator to the persistent store.
    """

    def __init__(self, session) -> None:
        self.session = session
        self.econ_repo = AgentEconomicStateRepository(session)
        self.effect_log_repo = EconomicEffectLogRepository(session)

    async def apply(
        self,
        delta: StateDelta,
        *,
        run_id: str,
        tick_no: int,
        world: WorldState | None = None,
    ) -> list[str]:
        """Apply a StateDelta to the database.

        Args:
            delta: The state delta to apply
            run_id: The simulation run ID
            tick_no: Current tick number
            world: Optional world state for additional validation

        Returns:
            List of agent IDs that were affected by the delta
        """
        affected: list[str] = []

        for agent_id, agent_delta in delta.agent_deltas.items():
            # Cash delta
            if agent_delta.cash_delta != 0:
                state = await self.econ_repo.get_for_agent(run_id, agent_id)
                if state is not None:
                    new_cash = state.cash + agent_delta.cash_delta
                    if new_cash >= 0:  # Validate non-negative cash
                        await self.econ_repo.add_cash(run_id, agent_id, agent_delta.cash_delta)
                        await self.effect_log_repo.create(
                            run_id=run_id,
                            agent_id=agent_id,
                            tick_no=tick_no,
                            effect_type=delta.effect_type,
                            cash_delta=agent_delta.cash_delta,
                            reason=delta.reason,
                        )
                    else:
                        logger.warning(
                            "delta_applier: cash would go negative for agent=%s, delta=%.2f, current=%.2f",
                            agent_id,
                            agent_delta.cash_delta,
                            state.cash,
                        )

            # Food security delta
            if agent_delta.food_security_delta != 0:
                current_state = await self.econ_repo.get_for_agent(run_id, agent_id)
                if current_state is not None:
                    new_food = max(
                        0.0, min(1.0, current_state.food_security + agent_delta.food_security_delta)
                    )
                    if new_food != current_state.food_security:
                        await self.econ_repo.update_food_security(
                            run_id, agent_id, agent_delta.food_security_delta
                        )
                        await self.effect_log_repo.create(
                            run_id=run_id,
                            agent_id=agent_id,
                            tick_no=tick_no,
                            effect_type=delta.effect_type,
                            cash_delta=0.0,
                            food_security_delta=agent_delta.food_security_delta,
                            reason=delta.reason,
                        )

            # Employment status delta
            if agent_delta.employment_status:
                await self.econ_repo.update_employment_status(
                    run_id, agent_id, agent_delta.employment_status
                )

            # Inventory changes would go here once inventory is implemented
            # For now, we log the intent but don't persist

            affected.append(agent_id)

        await self.session.commit()
        return affected

    async def apply_relationship_deltas(
        self,
        delta: StateDelta,
        *,
        run_id: str,
        tick_no: int,
    ) -> None:
        """Apply relationship deltas from a StateDelta.

        Args:
            delta: The state delta containing relationship changes
            run_id: The simulation run ID
            tick_no: Current tick number
        """
        for agent_pair, rel_delta in delta.relationship_deltas.items():
            # Parse agent pair (format: "agent1:agent2")
            if ":" not in agent_pair:
                logger.warning(
                    "delta_applier: invalid agent pair format: %s",
                    agent_pair,
                )
                continue

            agent1_id, agent2_id = agent_pair.split(":", 1)

            # Apply relationship changes via repository
            from app.store.repositories import RelationshipRepository

            rel_repo = RelationshipRepository(self.session)

            for target_id, delta_value in [(agent1_id, rel_delta), (agent2_id, rel_delta)]:
                if delta_value.familiarity_delta != 0:
                    await rel_repo.adjust_familiarity(
                        run_id,
                        target_id,
                        agent_pair.replace(":", "_"),
                        delta_value.familiarity_delta,
                    )
                if delta_value.trust_delta != 0:
                    await rel_repo.adjust_trust(
                        run_id, target_id, agent_pair.replace(":", "_"), delta_value.trust_delta
                    )
                if delta_value.affinity_delta != 0:
                    await rel_repo.adjust_affinity(
                        run_id, target_id, agent_pair.replace(":", "_"), delta_value.affinity_delta
                    )

        await self.session.commit()
