"""
LLM4s 핵심 모듈 단위 테스트
SilentLogger, 세션 CRUD, 리플레이, 자동 정리 기능을 검증합니다.
"""
import asyncio
import json
import tempfile
from pathlib import Path

import pytest
import aiosqlite

# 테스트 대상
from app.core.logging import SilentLogger


@pytest.fixture
def tmp_db(tmp_path):
    """임시 DB 경로를 반환합니다."""
    return tmp_path / "test_llm4s.db"


@pytest.fixture
def logger(tmp_db):
    """테스트용 SilentLogger 인스턴스를 반환합니다."""
    return SilentLogger(db_path=tmp_db)


@pytest.mark.asyncio
async def test_initialize_creates_tables(logger, tmp_db):
    """초기화 시 세 개의 테이블이 정상적으로 생성되는지 검증합니다."""
    await logger.initialize()

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute(
            "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
        ) as cursor:
            tables = [row[0] for row in await cursor.fetchall()]

    assert "sessions" in tables
    assert "intercept_logs" in tables
    assert "commands" in tables

    await logger.shutdown()


@pytest.mark.asyncio
async def test_create_session(logger):
    """세션이 올바르게 생성되고 ID가 반환되는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Google", "gemini-2.0-flash")
    assert isinstance(sid, int)
    assert sid > 0

    await logger.shutdown()


@pytest.mark.asyncio
async def test_session_crud(logger, tmp_db):
    """세션 생성, 이름 변경, 삭제 흐름을 검증합니다."""
    await logger.initialize()

    # 생성
    sid = await logger.create_session("OpenAI", "gpt-4")

    # 이름 변경
    await logger.rename_session(sid, "debug-test-01")

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute("SELECT name FROM sessions WHERE id = ?", (sid,)) as cursor:
            row = await cursor.fetchone()
    assert row[0] == "debug-test-01"

    # 삭제
    await logger.delete_session(sid)

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute("SELECT COUNT(*) FROM sessions WHERE id = ?", (sid,)) as cursor:
            row = await cursor.fetchone()
    assert row[0] == 0

    await logger.shutdown()


@pytest.mark.asyncio
async def test_log_message(logger, tmp_db):
    """log_message로 전달한 메시지가 DB에 기록되는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Anthropic", "claude-3")
    payload = json.dumps({"jsonrpc": "2.0", "method": "ping", "id": 1})
    await logger.log_message("C2S", payload.encode(), session_id=sid)

    # 워커가 큐를 처리할 시간을 줌
    await asyncio.sleep(0.3)

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute(
            "SELECT direction, method FROM intercept_logs WHERE session_id = ?", (sid,)
        ) as cursor:
            row = await cursor.fetchone()

    assert row is not None
    assert row[0] == "C2S"
    assert row[1] == "ping"

    await logger.shutdown()


@pytest.mark.asyncio
async def test_replay_and_pending_commands(logger, tmp_db):
    """replay_message로 주입한 명령이 get_pending_commands로 조회되는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Test", "test-model")
    payload = json.dumps({"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "echo"}})

    await logger.replay_message(sid, payload)

    # 대기 중인 명령 확인
    pending = await logger.get_pending_commands(sid)
    assert len(pending) == 1
    assert pending[0][1] == payload

    # sent로 마킹
    cmd_id = pending[0][0]
    await logger.mark_command_sent(cmd_id)

    # 다시 조회하면 비어있어야 함
    pending_after = await logger.get_pending_commands(sid)
    assert len(pending_after) == 0

    await logger.shutdown()


@pytest.mark.asyncio
async def test_token_estimation_korean(logger):
    """한글이 포함된 페이로드의 토큰 추정이 영문보다 높게 계산되는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Test", "test")

    # 영문 페이로드
    eng = json.dumps({"method": "test", "params": {"text": "hello world this is english"}})
    # 한글 페이로드 (비슷한 길이)
    kor = json.dumps({"method": "test", "params": {"text": "안녕하세요 세계 이것은 한국어입니다"}}, ensure_ascii=False)

    await logger.log_message("C2S", eng.encode(), session_id=sid)
    await logger.log_message("C2S", kor.encode(), session_id=sid)

    await asyncio.sleep(0.3)

    async with aiosqlite.connect(logger.db_path) as db:
        async with db.execute(
            "SELECT tokens FROM intercept_logs WHERE session_id = ? ORDER BY id ASC", (sid,)
        ) as cursor:
            rows = await cursor.fetchall()

    eng_tokens = rows[0][0]
    kor_tokens = rows[1][0]

    # 한글은 글자당 ~1.5토큰이므로 같은 의미의 문장이면 토큰 수가 더 높아야 함
    assert kor_tokens > 0
    assert eng_tokens > 0

    await logger.shutdown()


@pytest.mark.asyncio
async def test_cleanup_old_logs(logger, tmp_db):
    """cleanup_old_logs가 오래된 로그를 정상적으로 삭제하는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Test", "test")

    # 직접 오래된 로그 삽입 (8일 전)
    async with aiosqlite.connect(tmp_db) as db:
        await db.execute(
            "INSERT INTO intercept_logs (session_id, direction, method, payload, tokens, timestamp) "
            "VALUES (?, 'C2S', 'old_method', '{}', 10, datetime('now', '-8 days'))",
            (sid,)
        )
        await db.execute(
            "INSERT INTO intercept_logs (session_id, direction, method, payload, tokens) "
            "VALUES (?, 'C2S', 'new_method', '{}', 10)",
            (sid,)
        )
        await db.commit()

    await logger.cleanup_old_logs(days=7)

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute(
            "SELECT method FROM intercept_logs WHERE session_id = ?", (sid,)
        ) as cursor:
            rows = await cursor.fetchall()

    methods = [r[0] for r in rows]
    assert "old_method" not in methods
    assert "new_method" in methods

    await logger.shutdown()


@pytest.mark.asyncio
async def test_error_detection(logger, tmp_db):
    """에러가 포함된 페이로드가 is_error=True로 기록되는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Test", "test")

    # JSON-RPC 에러 응답
    error_payload = json.dumps({"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": 1})
    await logger.log_message("S2C", error_payload.encode(), session_id=sid)

    # 정상 응답
    ok_payload = json.dumps({"jsonrpc": "2.0", "result": {}, "id": 2})
    await logger.log_message("S2C", ok_payload.encode(), session_id=sid)

    await asyncio.sleep(0.3)

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute(
            "SELECT is_error, error_type FROM intercept_logs WHERE session_id = ? ORDER BY id ASC", (sid,)
        ) as cursor:
            rows = await cursor.fetchall()

    assert rows[0][0] == 1  # is_error = True
    assert rows[0][1] == "json_rpc"
    assert rows[1][0] == 0  # is_error = False

    await logger.shutdown()


@pytest.mark.asyncio
async def test_response_time_matching(logger, tmp_db):
    """C2S→S2C의 jsonrpc_id 매칭으로 응답시간이 계산되는지 검증합니다."""
    await logger.initialize()

    sid = await logger.create_session("Test", "test")

    # C2S 요청
    req = json.dumps({"jsonrpc": "2.0", "method": "tools/call", "id": 42})
    await logger.log_message("C2S", req.encode(), session_id=sid)

    await asyncio.sleep(0.5)  # 인위적 지연

    # S2C 응답 (같은 id)
    res = json.dumps({"jsonrpc": "2.0", "result": {"output": "hello"}, "id": 42})
    await logger.log_message("S2C", res.encode(), session_id=sid)

    await asyncio.sleep(0.5)

    async with aiosqlite.connect(tmp_db) as db:
        async with db.execute(
            "SELECT jsonrpc_id, response_time_ms FROM intercept_logs WHERE session_id = ? ORDER BY id ASC", (sid,)
        ) as cursor:
            rows = await cursor.fetchall()

    # C2S: jsonrpc_id가 기록되어야 함
    assert rows[0][0] == "42"
    assert rows[0][1] is None  # C2S에는 response_time 없음

    # S2C: jsonrpc_id + response_time_ms가 기록되어야 함
    assert rows[1][0] == "42"
    assert rows[1][1] is not None
    assert rows[1][1] > 0  # 응답시간이 양수

    await logger.shutdown()
