import json

from sqlalchemy import select

from services.routing_service import engine as routing_engine
from services.shared.core import new_id, utc_now_iso
from services.shared.db import get_session
from services.shared.sql_init import init_sql_schema
from services.shared.sql_models import AgentRow


def _make_agent(session, *, level="L2", tenant_ids=None, skills=None, status="AVAILABLE", enabled=True):
    now = utc_now_iso()
    row = AgentRow(
        agent_id=new_id("agt"),
        tenant_ids_json=json.dumps(tenant_ids or []),
        extension="2001",
        endpoint=None,
        display_name="Test Agent",
        level=level,
        skills_json=json.dumps(skills or []),
        status=status,
        max_concurrent_calls=1,
        enabled=enabled,
        created_at=now,
        updated_at=now,
    )
    session.add(row)
    session.commit()
    session.refresh(row)
    return row


def test_reserve_agent_picks_available_agent_of_requested_level():
    init_sql_schema()
    session = get_session()
    try:
        agent_l2 = _make_agent(session, level="L2")
        _make_agent(session, level="L3")

        reserved = routing_engine.reserve_agent(
            session,
            call_id="call-1",
            level="L2",
            tenant_id=None,
            required_skills=[],
        )
        assert reserved is not None
        assert reserved.agent_id == agent_l2.agent_id
        assert reserved.status == "RESERVED"
        assert reserved.current_call_id == "call-1"
    finally:
        session.close()


def test_reserve_agent_does_not_double_reserve():
    init_sql_schema()
    session = get_session()
    try:
        _make_agent(session, level="L2")

        first = routing_engine.reserve_agent(session, call_id="call-a", level="L2", tenant_id=None, required_skills=[])
        second = routing_engine.reserve_agent(session, call_id="call-b", level="L2", tenant_id=None, required_skills=[])
        assert first is not None
        assert second is None
    finally:
        session.close()


def test_reserve_agent_filters_by_tenant_and_skills():
    init_sql_schema()
    session = get_session()
    try:
        wrong_tenant = _make_agent(session, level="L2", tenant_ids=["other"])
        no_skill = _make_agent(session, level="L2", tenant_ids=["konturai"], skills=[])
        matching = _make_agent(session, level="L2", tenant_ids=["konturai"], skills=["billing"])

        reserved = routing_engine.reserve_agent(
            session,
            call_id="call-skill",
            level="L2",
            tenant_id="konturai",
            required_skills=["billing"],
        )
        assert reserved is not None
        assert reserved.agent_id == matching.agent_id
        assert reserved.agent_id != wrong_tenant.agent_id
        assert reserved.agent_id != no_skill.agent_id
    finally:
        session.close()


def test_release_agent_by_call_id_makes_agent_available_again():
    init_sql_schema()
    session = get_session()
    try:
        agent = _make_agent(session, level="L2")
        reserved = routing_engine.reserve_agent(session, call_id="call-z", level="L2", tenant_id=None, required_skills=[])
        assert reserved is not None

        released = routing_engine.release_agent_by_call_id(session, call_id="call-z")
        assert released is not None
        assert released.agent_id == agent.agent_id
        assert released.status == "AVAILABLE"
        assert released.current_call_id is None
        assert released.calls_handled_count == 1

        again = routing_engine.reserve_agent(session, call_id="call-again", level="L2", tenant_id=None, required_skills=[])
        assert again is not None
        assert again.agent_id == agent.agent_id
    finally:
        session.close()


def test_reserve_agent_excludes_disabled_and_excluded_ids():
    init_sql_schema()
    session = get_session()
    try:
        disabled = _make_agent(session, level="L2", enabled=False)
        excluded = _make_agent(session, level="L2")
        available = _make_agent(session, level="L2")

        reserved = routing_engine.reserve_agent(
            session,
            call_id="call-exc",
            level="L2",
            tenant_id=None,
            required_skills=[],
            exclude_agent_ids=[excluded.agent_id],
        )
        assert reserved is not None
        assert reserved.agent_id == available.agent_id
        assert reserved.agent_id != disabled.agent_id
        assert reserved.agent_id != excluded.agent_id
    finally:
        session.close()
