"""Automation management API routes."""

import secrets

from fastapi import APIRouter, Depends, HTTPException, Request

from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
    fire_entity_event,
    get_automation_engine,
    get_automation_store,
    get_scene_preset_store,
)
from wled_controller.api.schemas.automations import (
    AutomationCreate,
    AutomationListResponse,
    AutomationResponse,
    AutomationUpdate,
    RuleSchema,
)
from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.storage.automation import (
    ApplicationRule,
    DisplayStateRule,
    HomeAssistantRule,
    MQTTRule,
    Rule,
    StartupRule,
    SystemIdleRule,
    TimeOfDayRule,
    WebhookRule,
)
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.utils import get_logger
from wled_controller.storage.base_store import EntityNotFoundError

logger = get_logger(__name__)
router = APIRouter()


# ===== Helpers =====


def _rule_from_schema(s: RuleSchema) -> Rule:
    _SCHEMA_TO_RULE = {
        "application": lambda: ApplicationRule(
            apps=s.apps or [],
            match_type=s.match_type or "running",
        ),
        "time_of_day": lambda: TimeOfDayRule(
            start_time=s.start_time or "00:00",
            end_time=s.end_time or "23:59",
        ),
        "system_idle": lambda: SystemIdleRule(
            idle_minutes=s.idle_minutes if s.idle_minutes is not None else 5,
            when_idle=s.when_idle if s.when_idle is not None else True,
        ),
        "display_state": lambda: DisplayStateRule(
            state=s.state or "on",
        ),
        "mqtt": lambda: MQTTRule(
            mqtt_source_id=s.mqtt_source_id or "",
            topic=s.topic or "",
            payload=s.payload or "",
            match_mode=s.match_mode or "exact",
        ),
        "webhook": lambda: WebhookRule(
            token=s.token or secrets.token_hex(16),
        ),
        "startup": lambda: StartupRule(),
        "home_assistant": lambda: HomeAssistantRule(
            ha_source_id=s.ha_source_id or "",
            entity_id=s.entity_id or "",
            state=s.state or "",
            match_mode=s.match_mode or "exact",
        ),
    }
    factory = _SCHEMA_TO_RULE.get(s.rule_type)
    if factory is None:
        raise ValueError(f"Unknown rule type: {s.rule_type}")
    return factory()


def _rule_to_schema(r: Rule) -> RuleSchema:
    d = r.to_dict()
    return RuleSchema(**d)


def _automation_to_response(
    automation, engine: AutomationEngine, request: Request = None
) -> AutomationResponse:
    state = engine.get_automation_state(automation.id)

    # Build webhook URL from the first webhook rule (if any)
    webhook_url = None
    for r in automation.rules:
        if isinstance(r, WebhookRule) and r.token:
            # Prefer configured external URL, fall back to request base URL
            from wled_controller.api.routes.system import load_external_url

            ext = load_external_url()
            if ext:
                webhook_url = ext + f"/api/v1/webhooks/{r.token}"
            elif request:
                webhook_url = str(request.base_url).rstrip("/") + f"/api/v1/webhooks/{r.token}"
            else:
                webhook_url = f"/api/v1/webhooks/{r.token}"
            break

    return AutomationResponse(
        id=automation.id,
        name=automation.name,
        enabled=automation.enabled,
        rule_logic=automation.rule_logic,
        rules=[_rule_to_schema(r) for r in automation.rules],
        scene_preset_id=automation.scene_preset_id,
        deactivation_mode=automation.deactivation_mode,
        deactivation_scene_preset_id=automation.deactivation_scene_preset_id,
        webhook_url=webhook_url,
        is_active=state["is_active"],
        last_activated_at=state.get("last_activated_at"),
        last_deactivated_at=state.get("last_deactivated_at"),
        tags=automation.tags,
        created_at=automation.created_at,
        updated_at=automation.updated_at,
    )


def _validate_rule_logic(logic: str) -> None:
    if logic not in ("or", "and"):
        raise HTTPException(
            status_code=400, detail=f"Invalid rule_logic: {logic}. Must be 'or' or 'and'."
        )


def _validate_scene_refs(
    scene_preset_id: str | None,
    deactivation_scene_preset_id: str | None,
    scene_store: ScenePresetStore,
) -> None:
    """Validate that referenced scene preset IDs exist."""
    for sid, label in [
        (scene_preset_id, "scene_preset_id"),
        (deactivation_scene_preset_id, "deactivation_scene_preset_id"),
    ]:
        if sid is not None:
            try:
                scene_store.get_preset(sid)
            except ValueError:
                raise HTTPException(
                    status_code=400, detail=f"Scene preset not found: {sid} ({label})"
                )


# ===== CRUD Endpoints =====


@router.post(
    "/api/v1/automations",
    response_model=AutomationResponse,
    tags=["Automations"],
    status_code=201,
)
async def create_automation(
    request: Request,
    data: AutomationCreate,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
    scene_store: ScenePresetStore = Depends(get_scene_preset_store),
):
    """Create a new automation."""
    _validate_rule_logic(data.rule_logic)
    _validate_scene_refs(data.scene_preset_id, data.deactivation_scene_preset_id, scene_store)

    try:
        rules = [_rule_from_schema(r) for r in data.rules]
    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    automation = store.create_automation(
        name=data.name,
        enabled=data.enabled,
        rule_logic=data.rule_logic,
        rules=rules,
        scene_preset_id=data.scene_preset_id,
        deactivation_mode=data.deactivation_mode,
        deactivation_scene_preset_id=data.deactivation_scene_preset_id,
        tags=data.tags,
    )

    if automation.enabled:
        await engine.trigger_evaluate()

    fire_entity_event("automation", "created", automation.id)
    return _automation_to_response(automation, engine, request)


@router.get(
    "/api/v1/automations",
    response_model=AutomationListResponse,
    tags=["Automations"],
)
async def list_automations(
    request: Request,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
):
    """List all automations."""
    automations = store.get_all_automations()
    return AutomationListResponse(
        automations=[_automation_to_response(a, engine, request) for a in automations],
        count=len(automations),
    )


@router.get(
    "/api/v1/automations/{automation_id}",
    response_model=AutomationResponse,
    tags=["Automations"],
)
async def get_automation(
    request: Request,
    automation_id: str,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
):
    """Get a single automation."""
    try:
        automation = store.get_automation(automation_id)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    return _automation_to_response(automation, engine, request)


@router.put(
    "/api/v1/automations/{automation_id}",
    response_model=AutomationResponse,
    tags=["Automations"],
)
async def update_automation(
    request: Request,
    automation_id: str,
    data: AutomationUpdate,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
    scene_store: ScenePresetStore = Depends(get_scene_preset_store),
):
    """Update an automation."""
    if data.rule_logic is not None:
        _validate_rule_logic(data.rule_logic)

    # Validate scene refs (only the ones being updated)
    _validate_scene_refs(data.scene_preset_id, data.deactivation_scene_preset_id, scene_store)

    rules = None
    if data.rules is not None:
        try:
            rules = [_rule_from_schema(r) for r in data.rules]
        except EntityNotFoundError as e:
            raise HTTPException(status_code=404, detail=str(e))

        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))

    try:
        # If disabling, deactivate first
        if data.enabled is False:
            await engine.deactivate_if_active(automation_id)

        # Build update kwargs — use sentinel for Optional[str] fields
        update_kwargs = dict(
            automation_id=automation_id,
            name=data.name,
            enabled=data.enabled,
            rule_logic=data.rule_logic,
            rules=rules,
            deactivation_mode=data.deactivation_mode,
            tags=data.tags,
        )
        if data.scene_preset_id is not None:
            update_kwargs["scene_preset_id"] = data.scene_preset_id
        if data.deactivation_scene_preset_id is not None:
            update_kwargs["deactivation_scene_preset_id"] = data.deactivation_scene_preset_id

        automation = store.update_automation(**update_kwargs)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    # Re-evaluate immediately if automation is enabled (may have new rules/scene)
    if automation.enabled:
        await engine.trigger_evaluate()

    fire_entity_event("automation", "updated", automation_id)
    return _automation_to_response(automation, engine, request)


@router.delete(
    "/api/v1/automations/{automation_id}",
    status_code=204,
    tags=["Automations"],
)
async def delete_automation(
    automation_id: str,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
):
    """Delete an automation."""
    # Deactivate first
    await engine.deactivate_if_active(automation_id)

    try:
        store.delete_automation(automation_id)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    fire_entity_event("automation", "deleted", automation_id)


# ===== Enable/Disable =====


@router.post(
    "/api/v1/automations/{automation_id}/enable",
    response_model=AutomationResponse,
    tags=["Automations"],
)
async def enable_automation(
    request: Request,
    automation_id: str,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
):
    """Enable an automation."""
    try:
        automation = store.update_automation(automation_id=automation_id, enabled=True)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    # Evaluate immediately so scene activates without waiting for the next poll cycle
    await engine.trigger_evaluate()

    return _automation_to_response(automation, engine, request)


@router.post(
    "/api/v1/automations/{automation_id}/disable",
    response_model=AutomationResponse,
    tags=["Automations"],
)
async def disable_automation(
    request: Request,
    automation_id: str,
    _auth: AuthRequired,
    store: AutomationStore = Depends(get_automation_store),
    engine: AutomationEngine = Depends(get_automation_engine),
):
    """Disable an automation and deactivate it."""
    await engine.deactivate_if_active(automation_id)

    try:
        automation = store.update_automation(automation_id=automation_id, enabled=False)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    return _automation_to_response(automation, engine, request)
