from __future__ import annotations

import json
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import DateTime, select

from app.core.database import session_scope
from app.modules.zootech.catalog_models import (
    ZootechDailyComponentNormAdjustment,
    ZootechDailyIngredientReplacement,
    ZootechDailyIngredientSkip,
    ZootechDailyTripSkip,
    ZootechDailyUnloadingGroupSkip,
    ZootechFeedDispenser,
    ZootechFeedingLocation,
    ZootechFeedingPeriod,
    ZootechFeedingPoint,
    ZootechFeedMixer,
    ZootechPeriodRecipe,
    ZootechTrip,
    ZootechUnloadingGroup,
)
from app.modules.zootech.models import ZootechComponent, ZootechIngredient, ZootechRecipe

TABLE_MODEL_MAP = {
    "component": ZootechComponent,
    "recipe": ZootechRecipe,
    "ingredient": ZootechIngredient,
    "unloading_group": ZootechUnloadingGroup,
    "feed_mixer": ZootechFeedMixer,
    "feed_dispenser": ZootechFeedDispenser,
    "feeding_location": ZootechFeedingLocation,
    "feeding_period": ZootechFeedingPeriod,
    "feeding_point": ZootechFeedingPoint,
    "period_recipes": ZootechPeriodRecipe,
    "daily_trip_skip": ZootechDailyTripSkip,
    "daily_ingredient_skip": ZootechDailyIngredientSkip,
    "daily_unloading_group_skip": ZootechDailyUnloadingGroupSkip,
    "daily_ingredient_replacement": ZootechDailyIngredientReplacement,
    "daily_component_norm_adjustment": ZootechDailyComponentNormAdjustment,
    "trip": ZootechTrip,
}


def _coerce_for_model(model, key: str, value: Any) -> Any:
    if value is None:
        return value
    column = model.__table__.columns.get(key)
    if column is None:
        return value
    if isinstance(column.type, DateTime) and isinstance(value, str):
        try:
            return datetime.fromisoformat(value.replace("Z", "+00:00"))
        except ValueError:
            return datetime.now(UTC)
    return value


def _parse_period_recipe_id(record_id: str) -> tuple[str, str]:
    if ":" in record_id:
        period_id, recipe_id = record_id.split(":", 1)
        return period_id, recipe_id
    return record_id, record_id


def load_catalog_row(enterprise_id: str, table_name: str, record_id: str) -> dict[str, Any] | None:
    model = TABLE_MODEL_MAP.get(table_name)
    if not model:
        return None
    with session_scope() as db:
        if model is ZootechPeriodRecipe:
            period_id, recipe_id = _parse_period_recipe_id(record_id)
            row = db.scalar(
                select(model).where(
                    model.enterprise_id == enterprise_id,
                    model.period_id == period_id,
                    model.recipe_id == recipe_id,
                )
            )
        else:
            row = db.scalar(
                select(model).where(model.enterprise_id == enterprise_id, model.id == record_id)
            )
        if not row:
            return None
        return _row_to_dict(row, table_name)


def apply_catalog_change(
    enterprise_id: str,
    table_name: str,
    record_id: str,
    action: str,
    payload: dict[str, Any],
    version: int,
    content_hash: str,
) -> None:
    model = TABLE_MODEL_MAP.get(table_name)
    if not model:
        return
    with session_scope() as db:
        if model is ZootechPeriodRecipe:
            period_id, recipe_id = _parse_period_recipe_id(record_id)
            row = db.scalar(
                select(model).where(
                    model.enterprise_id == enterprise_id,
                    model.period_id == period_id,
                    model.recipe_id == recipe_id,
                )
            )
        else:
            row = db.scalar(
                select(model).where(model.enterprise_id == enterprise_id, model.id == record_id)
            )
        if action == "delete":
            if row:
                row.is_deleted = True
                row.version = version
                row.content_hash = content_hash
                row.updated_at = datetime.now(UTC)
            return
        data = dict(payload)
        data["enterprise_id"] = enterprise_id
        data["version"] = version
        data["content_hash"] = content_hash
        data["is_deleted"] = False
        if model is ZootechPeriodRecipe:
            data["period_id"] = data.get("period_id") or _parse_period_recipe_id(record_id)[0]
            data["recipe_id"] = data.get("recipe_id") or _parse_period_recipe_id(record_id)[1]
        else:
            data["id"] = record_id
        if row:
            _apply_fields(row, data, model)
            row.updated_at = datetime.now(UTC)
        else:
            allowed = {c.key for c in model.__table__.columns}
            filtered = {
                k: _coerce_for_model(model, k, v) for k, v in data.items() if k in allowed
            }
            extra = {k: v for k, v in data.items() if k not in allowed}
            if extra and "payload_json" in allowed:
                filtered["payload_json"] = json.dumps(extra, ensure_ascii=False)
            db.add(model(**filtered))


def _apply_fields(row, data: dict[str, Any], model) -> None:
    allowed = {c.key for c in model.__table__.columns}
    extra: dict[str, Any] = {}
    for key, value in data.items():
        if key in allowed and key not in ("enterprise_id", "created_at"):
            setattr(row, key, _coerce_for_model(model, key, value))
        elif key not in ("enterprise_id", "created_at", "id"):
            extra[key] = value
    if model is ZootechRecipe and "ingredients" in data:
        row.payload_json = json.dumps(data.get("ingredients"), ensure_ascii=False)
    elif extra and "payload_json" in allowed:
        row.payload_json = json.dumps(extra, ensure_ascii=False)


def _row_to_dict(row, table_name: str) -> dict[str, Any]:
    result = {}
    for col in row.__table__.columns:
        val = getattr(row, col.key)
        if isinstance(val, datetime):
            val = val.isoformat()
        result[col.key] = val
    if isinstance(row, ZootechRecipe) and row.payload_json:
        try:
            parsed = json.loads(row.payload_json)
            if isinstance(parsed, list):
                result["ingredients"] = parsed
        except json.JSONDecodeError:
            pass
    if table_name == "period_recipes":
        result["id"] = f"{row.period_id}:{row.recipe_id}"
    elif hasattr(row, "payload_json") and row.payload_json and table_name != "recipe":
        try:
            result.update(json.loads(row.payload_json))
        except json.JSONDecodeError:
            pass
    return result
