from __future__ import annotations

import json
import os

from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError

from services.shared.core import Role, new_id, utc_now_iso
from services.shared.db import get_session
from services.shared.models import HealthResponse, VoiceEventIn, VoiceEventOut
from services.shared.security import get_actor
from services.shared.sql_init import init_sql_schema
from services.shared.sql_models import VoiceEventRow

app = FastAPI(title="voice-adapter-service", version="1.0.0")

init_sql_schema()


def _trusted_service_subjects() -> set[str]:
    raw = os.getenv("VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS", "svc:asterisk-bridge,svc:ivr-service")
    return {item.strip() for item in raw.split(",") if item.strip()}


def _is_trusted_service_actor(actor: dict) -> bool:
    return (
        actor.get("auth_source") == "service"
        and str(actor.get("sub") or "").strip() in _trusted_service_subjects()
    )


def _require_voice_event_ingest(actor: dict = Depends(get_actor)) -> dict:
    if actor.get("auth_source") == "service":
        if _is_trusted_service_actor(actor):
            return actor
        raise HTTPException(status_code=403, detail="Untrusted service subject")

    if actor.get("role") in {Role.ADMIN.value, Role.SUPERVISOR.value, Role.OPERATOR.value}:
        return actor
    raise HTTPException(status_code=403, detail="Insufficient role")


def _require_voice_event_read(actor: dict = Depends(get_actor)) -> dict:
    if actor.get("auth_source") == "service":
        if _is_trusted_service_actor(actor):
            return actor
        raise HTTPException(status_code=403, detail="Untrusted service subject")

    if actor.get("role") in {Role.ADMIN.value, Role.SUPERVISOR.value, Role.ANALYST.value}:
        return actor
    raise HTTPException(status_code=403, detail="Insufficient role")


def _to_out(row: VoiceEventRow) -> VoiceEventOut:
    return VoiceEventOut(
        event_id=row.event_id,
        event_type=row.event_type,
        call_id=row.call_id,
        interaction_id=row.interaction_id,
        source_event_id=row.source_event_id,
        payload=json.loads(row.payload_json or "{}"),
        created_at=row.created_at,
    )


def _load_existing_by_source_event(session, source_event_id: str | None) -> VoiceEventRow | None:
    if not source_event_id:
        return None
    return session.execute(
        select(VoiceEventRow).where(VoiceEventRow.source_event_id == source_event_id)
    ).scalar_one_or_none()


@app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
    return HealthResponse(status="ok", service="voice-adapter-service")


@app.post("/integrations/voice/events", response_model=VoiceEventOut)
def ingest_voice_event(
    payload: VoiceEventIn,
    _: dict = Depends(_require_voice_event_ingest),
) -> VoiceEventOut:
    session = get_session()
    try:
        existing = _load_existing_by_source_event(session, payload.source_event_id)
        if existing is not None:
            return _to_out(existing)

        row = VoiceEventRow(
            event_id=new_id("vev"),
            event_type=payload.event_type,
            call_id=payload.call_id,
            interaction_id=payload.interaction_id,
            source_event_id=payload.source_event_id,
            payload_json=json.dumps(payload.payload, ensure_ascii=False),
            created_at=utc_now_iso(),
        )
        session.add(row)
        try:
            session.commit()
        except IntegrityError:
            session.rollback()
            existing = _load_existing_by_source_event(session, payload.source_event_id)
            if existing is not None:
                return _to_out(existing)
            raise
        session.refresh(row)
        return _to_out(row)
    finally:
        session.close()


@app.get("/integrations/voice/events", response_model=list[VoiceEventOut])
def list_voice_events(
    limit: int = 100,
    _: dict = Depends(_require_voice_event_read),
) -> list[VoiceEventOut]:
    session = get_session()
    try:
        rows = session.execute(
            select(VoiceEventRow).order_by(VoiceEventRow.id.desc()).limit(max(limit, 1))
        ).scalars().all()
        return [_to_out(r) for r in reversed(rows)]
    finally:
        session.close()
