import asyncio
import json
import logging
import sqlite3
from pathlib import Path
from datetime import datetime
from typing import Optional

import aiosqlite

from app.core.config import DB_PATH

# 기본 로그 설정 (TUI나 CLI 출력 대신 파일로 내부 에러를 기록하기 위함)
logger = logging.getLogger(__name__)

class SilentLogger:
    """
    LLM4s의 핵심: "성능 저하 없는 기록"을 위한 비동기 SQLite 로거입니다.
    네트워크(stdio) 통신을 가로막지 않도록 asyncio.Queue를 활용하여 백그라운드에서 DB에 씁니다.
    """
    def __init__(self, db_path: Optional[Path] = None):
        self.db_path = db_path or DB_PATH
        # 하드닝: 큐 크기를 제한하여 급격한 트래픽 증가 시 메모리 폭증 방지
        self.queue: asyncio.Queue = asyncio.Queue(maxsize=10000)
        self._worker_task: Optional[asyncio.Task] = None
        self._db: Optional[aiosqlite.Connection] = None

    async def initialize(self):
        """DB 세션 및 테이블을 초기화하고 백그라운드 워커를 시작합니다."""
        self._db = await aiosqlite.connect(self.db_path)
        # DB 파일 권한을 소유자 전용으로 제한 (민감한 MCP 트래픽 보호)
        for suffix in ("", "-wal", "-shm"):
            p = Path(str(self.db_path) + suffix)
            if p.exists():
                try:
                    import os as _os
                    _os.chmod(p, 0o600)
                except OSError:
                    pass
        # WAL 모드 + 동시성 안정화 PRAGMA
        await self._db.execute("PRAGMA journal_mode=WAL")
        await self._db.execute("PRAGMA busy_timeout=5000")   # write 충돌 시 5초 대기 (SQLITE_BUSY 방지)
        await self._db.execute("PRAGMA synchronous=NORMAL")  # WAL에서 안전, 성능 ~30% 향상
        await self._db.execute("PRAGMA cache_size=-32000")   # 32MB 페이지 캐시
        await self._db.execute("PRAGMA temp_store=MEMORY")   # 임시 테이블 메모리 처리
        
        # 1. 세션 테이블 (K9s Pods와 매핑되는 개념)
        await self._db.execute("""
            CREATE TABLE IF NOT EXISTS sessions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT UNIQUE,               -- K9s 추천 식별자 (예: llm4s-x3k2r)
                provider TEXT,
                model TEXT,
                status TEXT DEFAULT 'RUNNING',  -- RUNNING, IDLE, COMPLETED, ERROR
                start_time DATETIME DEFAULT CURRENT_TIMESTAMP,
                end_time DATETIME,
                cwd TEXT,                       -- proxy 시작 시 작업 디렉토리
                project_name TEXT,              -- 프로젝트 식별자 (cwd의 basename 또는 명시값)
                inject_source TEXT DEFAULT 'manual'  -- 'claude_desktop' | 'manual'
            )
        """)

        # 2. 로그 테이블 (JSON-RPC 원본 기록 및 방향)
        await self._db.execute("""
            CREATE TABLE IF NOT EXISTS intercept_logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                session_id INTEGER,       -- 어떤 세션에서 발생한 로그인지 연결
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                direction TEXT NOT NULL,  -- 'C2S' (Client to Server) or 'S2C' (Server to Client)
                method TEXT,              -- 추출된 JSON-RPC 메서드 (예: 'read_file')
                payload TEXT NOT NULL,    -- 실제 JSON 페이로드 전체
                tokens INTEGER,           -- 사용된 토큰 수 (추정치 포함)
                size_bytes INTEGER,       -- 페이로드 크기 (bytes)
                FOREIGN KEY (session_id) REFERENCES sessions(id)
            )
        """)
        
        # 3. 명령 주입 테이블 (Replay 기능을 위한 IPC 채널)
        await self._db.execute("""
            CREATE TABLE IF NOT EXISTS commands (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                session_id INTEGER,
                payload TEXT NOT NULL,
                status TEXT DEFAULT 'pending', -- 'pending', 'sent', 'error'
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (session_id) REFERENCES sessions(id)
            )
        """)
        
        # 하위 호환성: 컬럼들 추가 (이미 존재하면 무시)
        migrations = [
            ("ALTER TABLE sessions ADD COLUMN name TEXT", "sessions.name"),
            ("ALTER TABLE sessions ADD COLUMN cwd TEXT", "sessions.cwd"),
            ("ALTER TABLE sessions ADD COLUMN project_name TEXT", "sessions.project_name"),
            ("ALTER TABLE sessions ADD COLUMN inject_source TEXT DEFAULT 'manual'", "sessions.inject_source"),
            ("ALTER TABLE intercept_logs ADD COLUMN session_id INTEGER", "intercept_logs.session_id"),
            ("ALTER TABLE intercept_logs ADD COLUMN tokens INTEGER", "intercept_logs.tokens"),
            ("ALTER TABLE intercept_logs ADD COLUMN size_bytes INTEGER", "intercept_logs.size_bytes"),
            ("ALTER TABLE intercept_logs ADD COLUMN is_error BOOLEAN DEFAULT 0", "intercept_logs.is_error"),
            ("ALTER TABLE intercept_logs ADD COLUMN error_type TEXT", "intercept_logs.error_type"),
            ("ALTER TABLE intercept_logs ADD COLUMN response_time_ms FLOAT", "intercept_logs.response_time_ms"),
            ("ALTER TABLE intercept_logs ADD COLUMN jsonrpc_id TEXT", "intercept_logs.jsonrpc_id"),
        ]
        for sql, col_name in migrations:
            try:
                await self._db.execute(sql)
                logger.info(f"Migration applied: {col_name}")
            except Exception as e:
                logger.debug(f"Migration skipped ({col_name}): {e}")

        # 성능 최적화 인덱스
        await self._db.execute(
            "CREATE INDEX IF NOT EXISTS idx_session_jsonrpc "
            "ON intercept_logs (session_id, jsonrpc_id)"
        )
        await self._db.execute(
            "CREATE INDEX IF NOT EXISTS idx_logs_session_id "
            "ON intercept_logs (session_id)"
        )
        await self._db.execute(
            "CREATE INDEX IF NOT EXISTS idx_sessions_provider "
            "ON sessions (provider)"
        )
        await self._db.execute(
            "CREATE INDEX IF NOT EXISTS idx_sessions_project "
            "ON sessions (project_name)"
        )

        await self._db.commit()

        # 큐를 소모하는 워커 태스크 시작
        self._worker_task = asyncio.create_task(self._log_worker())
        # 초기화 시 오래된 로그 정리
        await self.cleanup_old_logs()
        logger.info(f"SilentLogger initialized at {self.db_path}")

    async def shutdown(self):
        """앱 종료 시 남은 큐를 모두 비우고 DB를 안전하게 닫습니다."""
        # 큐에 남은 항목을 최대 5초간 소진한 뒤 워커 취소
        try:
            await asyncio.wait_for(self.queue.join(), timeout=5.0)
        except asyncio.TimeoutError:
            pass

        if self._worker_task:
            self._worker_task.cancel()
            try:
                await self._worker_task
            except asyncio.CancelledError:
                pass

        if self._db:
            await self._db.close()

    async def create_session(self, provider: str, model: str,
                              cwd: Optional[str] = None,
                              project_name: Optional[str] = None,
                              inject_source: str = "manual") -> int:
        """새로운 세션을 생성하고 ID를 반환합니다."""
        if not self._db:
            await self.initialize()

        import random, string
        suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=5))
        session_name = f"llm4s-{suffix}"

        # cwd/project_name 미전달 시 현재 디렉토리에서 자동 추출
        try:
            resolved_cwd = cwd or str(Path.cwd())
        except Exception:
            resolved_cwd = None
        resolved_project = project_name or (Path(resolved_cwd).name if resolved_cwd else None)

        async with self._db.execute(
            "INSERT INTO sessions (name, provider, model, cwd, project_name, inject_source) "
            "VALUES (?, ?, ?, ?, ?, ?)",
            (session_name, provider, model, resolved_cwd, resolved_project, inject_source)
        ) as cursor:
            session_id = cursor.lastrowid
        await self._db.commit()
        return session_id

    async def update_session_status(self, session_id: int, status: str):
        """세션 상태를 업데이트합니다."""
        if not self._db: return
        query = "UPDATE sessions SET status = ?"
        if status in ["COMPLETED", "ERROR"]:
            query += ", end_time = CURRENT_TIMESTAMP"
        query += " WHERE id = ?"
        await self._db.execute(query, (status, session_id))
        await self._db.commit()

    async def delete_session(self, session_id: int):
        """특정 세션과 그에 속한 모든 로그를 삭제합니다."""
        if not self._db: return
        await self._db.execute("DELETE FROM intercept_logs WHERE session_id = ?", (session_id,))
        await self._db.execute("DELETE FROM sessions WHERE id = ?", (session_id,))
        await self._db.commit()

    async def clear_sessions(self, provider: str, model: str):
        """특정 프로바이더와 모델의 모든 세션을 삭제합니다."""
        if not self._db: return
        # 해당되는 세션 ID 목록 가져오기
        async with self._db.execute(
            "SELECT id FROM sessions WHERE provider = ? AND model = ?", 
            (provider, model)
        ) as cursor:
            sids = [row[0] for row in await cursor.fetchall()]
        
        if sids:
            placeholders = ",".join(["?"] * len(sids))
            await self._db.execute(f"DELETE FROM intercept_logs WHERE session_id IN ({placeholders})", sids)
            await self._db.execute(f"DELETE FROM sessions WHERE id IN ({placeholders})", sids)
            await self._db.commit()

    async def rename_session(self, session_id: int, new_name: str):
        """세션 이름을 변경합니다."""
        if not self._db: return
        await self._db.execute("UPDATE sessions SET name = ? WHERE id = ?", (new_name, session_id))
        await self._db.commit()

    async def get_session_logs(self, session_id: int):
        """특정 세션의 모든 로그를 가져옵니다. (내보내기용)"""
        if not self._db: return []
        async with self._db.execute(
            "SELECT timestamp, direction, method, payload, tokens FROM intercept_logs WHERE session_id = ? ORDER BY id ASC",
            (session_id,)
        ) as cursor:
            return await cursor.fetchall()

    async def replay_message(self, session_id: int, payload: str):
        """메시지 재시도를 위해 명령 테이블에 주입합니다."""
        if not self._db: return
        await self._db.execute(
            "INSERT INTO commands (session_id, payload) VALUES (?, ?)",
            (session_id, payload)
        )
        await self._db.commit()

    async def get_pending_commands(self, session_id: int) -> list:
        """대기 중인 명령 목록을 반환합니다. (InterceptorProxy용 퍼블릭 인터페이스)"""
        if not self._db: return []
        try:
            async with self._db.execute(
                "SELECT id, payload FROM commands WHERE session_id = ? AND status = 'pending' ORDER BY id ASC",
                (session_id,)
            ) as cursor:
                return await cursor.fetchall()
        except Exception as e:
            logger.warning(f"Failed to fetch pending commands: {e}")
            return []

    async def mark_command_sent(self, command_id: int):
        """명령의 상태를 'sent'로 업데이트합니다."""
        if not self._db: return
        try:
            await self._db.execute(
                "UPDATE commands SET status = 'sent' WHERE id = ?", (command_id,)
            )
            await self._db.commit()
        except Exception as e:
            logger.warning(f"Failed to mark command {command_id} as sent: {e}")

    async def cleanup_old_logs(self, days: int = 7):
        """지정된 일수보다 오래된 로그와 세션을 자동 삭제합니다."""
        if not self._db: return
        try:
            # 오래된 로그 삭제
            await self._db.execute(
                "DELETE FROM intercept_logs WHERE timestamp < datetime('now', ?)",
                (f"-{days} days",)
            )
            # 로그가 모두 삭제된 완료/에러 세션 삭제
            await self._db.execute(
                "DELETE FROM sessions WHERE status IN ('COMPLETED', 'ERROR') "
                "AND start_time < datetime('now', ?) "
                "AND id NOT IN (SELECT DISTINCT session_id FROM intercept_logs WHERE session_id IS NOT NULL)",
                (f"-{days} days",)
            )
            # 오래된 명령 정리
            await self._db.execute(
                "DELETE FROM commands WHERE timestamp < datetime('now', ?)",
                (f"-{days} days",)
            )
            await self._db.commit()
            logger.info(f"Cleaned up logs older than {days} days.")
        except Exception as e:
            logger.warning(f"Failed to cleanup old logs: {e}")

    async def log_message(self, direction: str, payload_bytes: bytes, session_id: Optional[int] = None):
        """
        통신 스트림에서 메시지를 가로챘을 때 이 메서드를 호출합니다.
        await이지만 Queue에 넣기만 하므로 I/O 지연이 전혀 발생하지 않습니다.
        """
        payload_dict = {}
        payload_str = ""
        try:
            # JSON-RPC 추출 (단순 파싱용)
            payload_str = payload_bytes.decode('utf-8').strip()
            
            # [REPLAY] 태그가 있으면 제거하고 파싱 (TUI에서 주입한 명령 구분용)
            if payload_str.startswith("[REPLAY] "):
                payload_str = payload_str[9:].strip()
                
            payload_dict = json.loads(payload_str)
            method = payload_dict.get('method', 'unknown')
            
            # 여기서 실제 모델명이 payload에 있다면 추출해서 큐에 함께 보냄
            actual_model = None
            
            # C2S와 S2C 모두에서 모델명 추출 시도
            params = payload_dict.get("params", {})
            if isinstance(params, dict):
                actual_model = params.get("model")
                # messages 내부에서 모델명 검색 (OpenAI 호환 형식)
                if not actual_model and "messages" in params:
                    for msg in params.get("messages", []):
                        if isinstance(msg, dict) and "model" in msg:
                            actual_model = msg["model"]
                            break
            
            if not actual_model:
                actual_model = payload_dict.get("model")
            
            if not actual_model and "body" in payload_dict:
                actual_model = payload_dict.get("body", {}).get("model")
                    
        except Exception:
            payload_str = str(payload_bytes)
            method = "error_parsing"
            actual_model = None

        # 큐에 삽입 (즉시 리턴됨)
        # 토큰 추정: 영문 기준 약 4글자당 1토큰, 한글은 글자당 1~2토큰 (대략적인 추정치)
        # 실제 모델의 usage 정보가 있다면 나중에 정교화 가능
        size_bytes = len(payload_bytes)
        # 토큰 추정: 한글은 글자당 ~1.5토큰, 영문은 ~4글자당 1토큰
        korean_chars = sum(1 for c in payload_str if '\uac00' <= c <= '\ud7a3')
        ascii_chars = len(payload_str) - korean_chars
        estimated_tokens = int(korean_chars * 1.5) + (ascii_chars // 4)
        
        # --- Error Pattern Detection ---
        is_error = False
        error_type = None
        jsonrpc_id = None

        if isinstance(payload_dict, dict):
            # JSON-RPC id 추출 (C2S↔S2C 매칭용)
            raw_id = payload_dict.get("id")
            if raw_id is not None:
                jsonrpc_id = str(raw_id)

            # 에러 패턴 감지
            if "error" in payload_dict:
                is_error = True
                error_type = "json_rpc"
            elif payload_dict.get("status_code", 200) >= 400:
                is_error = True
                error_type = "http"
        
        if not is_error and "Traceback" in payload_str:
            is_error = True
            error_type = "traceback"

        await self.queue.put((direction, method, payload_str, session_id, actual_model,
                              estimated_tokens, size_bytes, is_error, error_type, jsonrpc_id))

    async def _log_worker(self):
        """
        [백그라운드 스레드] 큐에서 메시지를 꺼내서 DB에 쓰는 루프입니다.
        Client<->Server 통신 성능(스트림)에 전혀 영향을 주지 않습니다.
        """
        if not self._db:
            return

        while True:
            try:
                # 큐에 메시지가 들어올 때까지 대기
                (direction, method, payload, session_id, actual_model,
                 tokens, size_bytes, is_error, error_type, jsonrpc_id) = await self.queue.get()
                
                # --- Timing Profiler: S2C 응답 시 C2S와 매칭하여 응답시간 계산 ---
                response_time_ms = None
                if direction == "S2C" and jsonrpc_id and session_id:
                    try:
                        async with self._db.execute(
                            "SELECT timestamp FROM intercept_logs "
                            "WHERE session_id = ? AND direction = 'C2S' AND jsonrpc_id = ? "
                            "ORDER BY id DESC LIMIT 1",
                            (session_id, jsonrpc_id)
                        ) as cursor:
                            row = await cursor.fetchone()
                            if row:
                                from datetime import datetime
                                c2s_time = datetime.fromisoformat(row[0])
                                now = datetime.now()
                                response_time_ms = (now - c2s_time).total_seconds() * 1000
                    except Exception:
                        pass

                # DB에 기록
                await self._db.execute(
                    "INSERT INTO intercept_logs "
                    "(direction, method, payload, session_id, tokens, size_bytes, is_error, error_type, response_time_ms, jsonrpc_id) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                    (direction, method, payload, session_id, tokens, size_bytes,
                     is_error, error_type, response_time_ms, jsonrpc_id)
                )
                
                # 만약 실제 모델명을 찾았다면 해당 세션의 모델 정보를 업데이트
                if actual_model and session_id:
                    try:
                        await self._db.execute(
                            "UPDATE sessions SET model = ? WHERE id = ?",
                            (str(actual_model), session_id)
                        )
                    except Exception:
                        pass

                await self._db.commit()
                self.queue.task_done()
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in DB worker: {e}")

# 글로벌 싱글톤 인스턴스 (FastAPI와 Interceptor가 모두 접근할 수 있도록)
db_logger = SilentLogger()
