"""Fliiq daemon — lightweight FastAPI server for scheduler + job management."""

import asyncio
import os
import secrets
import time
from contextlib import asynccontextmanager
from pathlib import Path

import structlog
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse

from fliiq.runtime.agent.setup import resolve_llm_config
from fliiq.runtime.scheduler.executor import execute_job
from fliiq.runtime.scheduler.loader import load_jobs
from fliiq.runtime.scheduler.scheduler import Scheduler

log = structlog.get_logger()


@asynccontextmanager
async def _lifespan(app: FastAPI):
    """Start scheduler on boot, stop on shutdown."""
    # 1. Resolve project root + load env
    from dotenv import load_dotenv

    from fliiq.runtime.config import resolve_env_file, resolve_fliiq_dir

    env_path = resolve_env_file()
    if env_path:
        load_dotenv(env_path)

    if os.environ.get("FLIIQ_PROJECT_ROOT"):
        project_root = Path(os.environ["FLIIQ_PROJECT_ROOT"])
    else:
        project_root = resolve_fliiq_dir(require_local=True).parent
    jobs_dir = project_root / ".fliiq" / "jobs"
    jobs_dir.mkdir(parents=True, exist_ok=True)

    # 2. Resolve LLM config
    llm_config = resolve_llm_config()

    # 3. Load jobs + start scheduler
    jobs = load_jobs(jobs_dir)

    async def _on_fire(job):
        entry = await execute_job(job, jobs_dir, project_root, llm_config)
        if entry.status == "error":
            raise RuntimeError(f"Job failed: {entry.error}")

    scheduler = Scheduler(on_fire=_on_fire)
    await scheduler.start(jobs)

    # 4. Store state
    app.state.scheduler = scheduler
    app.state.llm_config = llm_config
    app.state.jobs_dir = jobs_dir
    app.state.project_root = project_root
    app.state.started_at = time.monotonic()

    # 5. Write PID file + generate daemon secret
    pid_path = project_root / ".fliiq" / "daemon.pid"
    pid_path.write_text(str(os.getpid()))

    secret_path = project_root / ".fliiq" / "daemon.secret"
    if not secret_path.exists():
        secret_path.parent.mkdir(parents=True, exist_ok=True)
        secret_path.write_text(secrets.token_hex(32))
        os.chmod(secret_path, 0o600)
    app.state.daemon_secret = secret_path.read_text().strip()

    # 6. Start Telegram listener (if configured)
    telegram_listener = None
    telegram_task = None
    telegram_bot_token = os.environ.get("TELEGRAM_BOT_TOKEN")
    if telegram_bot_token:
        from fliiq.runtime.telegram.listener import TelegramListener, parse_allowed_chat_ids

        allowed_ids = parse_allowed_chat_ids()
        if not allowed_ids:
            raise RuntimeError(
                "TELEGRAM_ALLOWED_CHAT_IDS is required when TELEGRAM_BOT_TOKEN is set. "
                "Run 'fliiq telegram setup' to configure it."
            )
        telegram_listener = TelegramListener(
            telegram_bot_token, llm_config, project_root, allowed_ids,
        )
        telegram_task = asyncio.create_task(telegram_listener.run())
        log.info("telegram_listener_started")

    app.state.telegram_active = telegram_listener is not None

    log.info("daemon_started", jobs=scheduler.scheduled_count, telegram=app.state.telegram_active, pid=os.getpid())

    try:
        yield
    finally:
        if telegram_listener:
            await telegram_listener.stop()
            telegram_task.cancel()
        await scheduler.stop()
        if pid_path.exists():
            pid_path.unlink()
        log.info("daemon_stopped")


app = FastAPI(title="Fliiq Daemon", lifespan=_lifespan)


@app.middleware("http")
async def _auth_middleware(request: Request, call_next):
    """Require Bearer token on /api/* routes. /health is exempt."""
    if request.url.path.startswith("/api/"):
        expected = getattr(request.app.state, "daemon_secret", None)
        if expected:
            auth = request.headers.get("authorization", "")
            if auth != f"Bearer {expected}":
                return JSONResponse(
                    status_code=401,
                    content={"detail": "Unauthorized. Provide Authorization: Bearer <daemon-secret>."},
                )
    return await call_next(request)


from fliiq.api.webhooks.router import router as webhook_router  # noqa: E402

app.include_router(webhook_router)


@app.get("/health")
async def health():
    uptime = int(time.monotonic() - app.state.started_at)
    return {
        "status": "ok",
        "jobs": app.state.scheduler.scheduled_count,
        "telegram": app.state.telegram_active,
        "uptime_s": uptime,
    }


@app.get("/api/jobs")
async def list_jobs():
    jobs = load_jobs(app.state.jobs_dir)
    return [j.model_dump(mode="json") for j in jobs]


@app.post("/api/jobs/{name}/run")
async def trigger_job(name: str):
    jobs = load_jobs(app.state.jobs_dir)
    job = next((j for j in jobs if j.name == name), None)
    if not job:
        raise HTTPException(status_code=404, detail=f"Job '{name}' not found")
    entry = await execute_job(
        job, app.state.jobs_dir, app.state.project_root, app.state.llm_config
    )
    return entry.model_dump(mode="json")
