"""LLM Tool Calling 실행기 — 사전 쿼리 + 동적 DB 조회."""

import json
import logging
import re
from typing import Optional

from config import qualified_table

logger = logging.getLogger(__name__)
VESSEL_ANALYSIS_RESULTS = qualified_table('vessel_analysis_results')
FLEET_VESSELS = qualified_table('fleet_vessels')
GROUP_POLYGON_SNAPSHOTS = qualified_table('group_polygon_snapshots')
GEAR_CORRELATION_SCORES = qualified_table('gear_correlation_scores')
CORRELATION_PARAM_MODELS = qualified_table('correlation_param_models')

# ── 사전 쿼리 패턴 (키워드 기반, 1회 왕복으로 해결) ──

_ZONE_MAP = {
    '수역1': 'ZONE_I', '수역 1': 'ZONE_I', '수역I': 'ZONE_I', 'ZONE_I': 'ZONE_I', '수역i': 'ZONE_I',
    '수역2': 'ZONE_II', '수역 2': 'ZONE_II', '수역II': 'ZONE_II', 'ZONE_II': 'ZONE_II',
    '수역3': 'ZONE_III', '수역 3': 'ZONE_III', '수역III': 'ZONE_III', 'ZONE_III': 'ZONE_III',
    '수역4': 'ZONE_IV', '수역 4': 'ZONE_IV', '수역IV': 'ZONE_IV', 'ZONE_IV': 'ZONE_IV',
    '영해': 'TERRITORIAL_SEA', '접속수역': 'CONTIGUOUS_ZONE',
}

_ACTIVITY_MAP = {
    '조업': 'FISHING', '어로': 'FISHING', '조업중': 'FISHING', '조업활동': 'FISHING',
    '정박': 'STATIONARY', '정지': 'STATIONARY', '대기': 'STATIONARY',
    '항행': 'SAILING', '이동': 'SAILING', '항해': 'SAILING',
}

_RISK_MAP = {
    '크리티컬': 'CRITICAL', 'critical': 'CRITICAL', '긴급': 'CRITICAL',
    '워치': 'HIGH', 'watch': 'HIGH', '경고': 'HIGH', '고위험': 'HIGH',
    '모니터': 'MEDIUM', 'monitor': 'MEDIUM', '주의': 'MEDIUM',
    '위험': None,  # 위험 선박 → CRITICAL+HIGH
}

_DARK_KEYWORDS = ['다크', '다크베셀', 'dark', 'ais 차단', 'ais차단', '신호차단']
_TRANSSHIP_KEYWORDS = ['환적', 'transshipment', '전재']
_SPOOF_KEYWORDS = ['스푸핑', 'spoofing', 'gps 조작', 'gps조작', '위치조작']


def detect_prequery(message: str) -> Optional[dict]:
    """사용자 메시지에서 사전 쿼리 패턴을 감지하여 DB 조회 파라미터 반환."""
    msg = message.lower().strip()
    params: dict = {}

    # 수역 감지
    for keyword, zone in _ZONE_MAP.items():
        if keyword.lower() in msg:
            params['zone'] = zone
            break

    # 활동 감지
    for keyword, activity in _ACTIVITY_MAP.items():
        if keyword in msg:
            params['activity'] = activity
            break

    # 위험도 감지
    for keyword, level in _RISK_MAP.items():
        if keyword in msg:
            if level:
                params['risk_level'] = level
            else:
                params['risk_levels'] = ['CRITICAL', 'HIGH']
            break

    # 다크베셀 감지
    if any(k in msg for k in _DARK_KEYWORDS):
        params['is_dark'] = True

    # 환적 감지
    if any(k in msg for k in _TRANSSHIP_KEYWORDS):
        params['is_transship'] = True

    # 스푸핑 감지
    if any(k in msg for k in _SPOOF_KEYWORDS):
        params['spoofing'] = True

    return params if params else None


def execute_prequery(params: dict) -> str:
    """사전 쿼리 패턴에 해당하는 DB 조회를 실행하여 결과를 텍스트로 반환."""
    try:
        from db import kcgdb

        conditions = ["analyzed_at > NOW() - INTERVAL '1 hour'"]
        bind_params: list = []

        if 'zone' in params:
            conditions.append('zone = %s')
            bind_params.append(params['zone'])

        if 'activity' in params:
            conditions.append('activity_state = %s')
            bind_params.append(params['activity'])

        if 'risk_level' in params:
            conditions.append('risk_level = %s')
            bind_params.append(params['risk_level'])
        elif 'risk_levels' in params:
            placeholders = ','.join(['%s'] * len(params['risk_levels']))
            conditions.append(f'risk_level IN ({placeholders})')
            bind_params.extend(params['risk_levels'])

        if params.get('is_dark'):
            conditions.append('is_dark = TRUE')

        if params.get('is_transship'):
            conditions.append('is_transship_suspect = TRUE')

        if params.get('spoofing'):
            conditions.append('spoofing_score > 0.5')

        where = ' AND '.join(conditions)

        query = f"""
            SELECT v.mmsi, v.risk_score, v.risk_level, v.zone, v.activity_state,
                   v.vessel_type, v.is_dark, v.gap_duration_min, v.spoofing_score,
                   v.cluster_id, v.cluster_size, v.dist_to_baseline_nm,
                   v.is_transship_suspect, v.transship_pair_mmsi,
                   fv.permit_no, fv.name_cn, fv.gear_code
            FROM {VESSEL_ANALYSIS_RESULTS} v
            LEFT JOIN {FLEET_VESSELS} fv ON v.mmsi = fv.mmsi
            WHERE {where}
            ORDER BY v.risk_score DESC
            LIMIT 30
        """

        with kcgdb.get_conn() as conn:
            with conn.cursor() as cur:
                cur.execute(query, bind_params)
                rows = cur.fetchall()

        if not rows:
            return '\n## 조회 결과\n해당 조건에 맞는 선박이 없습니다.\n'

        # 결과를 간략 테이블로 구성 (토큰 절약)
        lines = [f'\n## 조회 결과 ({len(rows)}척)']
        lines.append('| MMSI | 점수 | 수역 | 활동 | 허가 | 다크 |')
        lines.append('|---|---|---|---|---|---|')

        for row in rows[:15]:  # 최대 15척
            mmsi, risk_score, risk_level, zone, activity, vtype, is_dark, gap, spoof, \
                cid, csize, dist_nm, is_trans, trans_pair, permit, name_cn, gear = row
            permit_str = 'Y' if permit else 'N'
            dark_str = 'Y' if is_dark else '-'
            lines.append(f'| {mmsi} | {risk_score} | {zone} | {activity} | {permit_str} | {dark_str} |')

        return '\n'.join(lines)
    except Exception as e:
        logger.error('prequery execution failed: %s', e)
        return f'\n(DB 조회 실패: {e})\n'


# ── LLM Tool Calling 응답 파싱 + 실행 ──

_TOOL_CALL_PATTERN = re.compile(
    r'\{"tool"\s*:\s*"(\w+)"\s*,\s*"params"\s*:\s*(\{[^}]+\})\}',
)


def parse_tool_calls(llm_response: str) -> list[dict]:
    """LLM 응답에서 tool call JSON을 추출."""
    calls = []
    for match in _TOOL_CALL_PATTERN.finditer(llm_response):
        try:
            tool_name = match.group(1)
            params = json.loads(match.group(2))
            calls.append({'tool': tool_name, 'params': params})
        except json.JSONDecodeError:
            continue
    return calls[:2]  # 최대 2개


def execute_tool_call(call: dict) -> str:
    """단일 tool call 실행."""
    tool = call.get('tool', '')
    params = call.get('params', {})

    if tool == 'query_vessels':
        return execute_prequery(params)

    if tool == 'query_vessel_detail':
        mmsi = params.get('mmsi', '')
        if mmsi:
            from chat.context_builder import _build_vessel_detail
            return _build_vessel_detail(mmsi)
        return '(MMSI 미지정)'

    if tool == 'query_fleet_group':
        return _query_fleet_group(params)

    if tool == 'query_vessel_history':
        return _query_vessel_history(params)

    if tool == 'query_vessel_static':
        return _query_vessel_static(params)

    if tool == 'get_knowledge':
        return _get_knowledge(params)

    if tool == 'query_gear_correlation':
        return _query_gear_correlation(params)

    return f'(알 수 없는 도구: {tool})'


def _get_knowledge(params: dict) -> str:
    """도메인 지식 섹션 조회."""
    key = params.get('key', '')
    if not key:
        return '(key 미지정. 사용 가능: maritime_zones, fishing_agreement, algorithm_guide, response_guide, db_schema)'
    from chat.domain_knowledge import get_knowledge_section
    return get_knowledge_section(key)


def _query_fleet_group(params: dict) -> str:
    """선단/어구 그룹 조회."""
    try:
        from db import kcgdb

        conditions = [f"snapshot_time = (SELECT MAX(snapshot_time) FROM {GROUP_POLYGON_SNAPSHOTS})"]
        bind_params: list = []

        if 'group_type' in params:
            conditions.append('group_type = %s')
            bind_params.append(params['group_type'])
        if 'zone_id' in params:
            conditions.append('zone_id = %s')
            bind_params.append(params['zone_id'])

        where = ' AND '.join(conditions)
        query = f"""
            SELECT group_type, group_key, group_label, member_count, zone_name, members
            FROM {GROUP_POLYGON_SNAPSHOTS}
            WHERE {where}
            ORDER BY member_count DESC
            LIMIT 20
        """

        with kcgdb.get_conn() as conn:
            with conn.cursor() as cur:
                cur.execute(query, bind_params)
                rows = cur.fetchall()

        if not rows:
            return '\n(해당 조건의 그룹 없음)\n'

        lines = [f'\n## 그룹 조회 결과 ({len(rows)}건)']
        lines.append('| 유형 | 키 | 라벨 | 선박수 | 수역 |')
        lines.append('|---|---|---|---|---|')
        for row in rows:
            gtype, gkey, glabel, mcount, zname, members = row
            lines.append(f'| {gtype} | {gkey} | {glabel or "-"} | {mcount} | {zname or "-"} |')

        return '\n'.join(lines)
    except Exception as e:
        logger.error('fleet group query failed: %s', e)
        return f'\n(그룹 조회 실패: {e})\n'


def _query_vessel_history(params: dict) -> str:
    """snpdb에서 선박 항적 이력 조회 (daily 집계)."""
    try:
        from db import snpdb

        mmsi = params.get('mmsi', '')
        days = min(params.get('days', 7), 30)  # 최대 30일

        if not mmsi:
            return '(MMSI 미지정)'

        query = """
            SELECT time_bucket, distance_nm, avg_speed, max_speed, point_count,
                   start_position, end_position
            FROM signal.t_vessel_tracks_daily
            WHERE mmsi = %s AND time_bucket >= CURRENT_DATE - %s
            ORDER BY time_bucket DESC
        """

        with snpdb.get_conn() as conn:
            with conn.cursor() as cur:
                cur.execute(query, (mmsi, days))
                rows = cur.fetchall()

        if not rows:
            return f'\n(MMSI {mmsi}: 최근 {days}일 항적 데이터 없음)\n'

        lines = [f'\n## 항적 이력: {mmsi} (최근 {days}일)']
        lines.append('| 날짜 | 이동거리(NM) | 평균속도 | 최대속도 | AIS포인트 |')
        lines.append('|---|---|---|---|---|')
        for row in rows:
            dt, dist, avg_spd, max_spd, pts, start_pos, end_pos = row
            lines.append(
                f"| {dt} | {float(dist or 0):.1f} | {float(avg_spd or 0):.1f}kt "
                f"| {float(max_spd or 0):.1f}kt | {pts or 0} |"
            )

        return '\n'.join(lines)
    except Exception as e:
        logger.error('vessel history query failed: %s', e)
        return f'\n(항적 이력 조회 실패: {e})\n'


def _query_vessel_static(params: dict) -> str:
    """snpdb에서 선박 정적정보 + 변경 이력 조회."""
    try:
        from db import snpdb

        mmsi = params.get('mmsi', '')
        limit = min(params.get('limit', 10), 24)

        if not mmsi:
            return '(MMSI 미지정)'

        query = """
            SELECT time_bucket, name, vessel_type, length, width, draught,
                   destination, status, callsign, imo
            FROM signal.t_vessel_static
            WHERE mmsi = %s
            ORDER BY time_bucket DESC
            LIMIT %s
        """

        with snpdb.get_conn() as conn:
            with conn.cursor() as cur:
                cur.execute(query, (mmsi, limit))
                rows = cur.fetchall()

        if not rows:
            return f'\n(MMSI {mmsi}: 정적정보 없음)\n'

        # 최신 정보
        latest = rows[0]
        lines = [f'\n## 선박 정적정보: {mmsi}']
        lines.append(f'- 선명: {latest[1] or "N/A"}')
        lines.append(f'- 선종: {latest[2] or "N/A"}')
        lines.append(f'- 제원: L={latest[3] or 0}m × W={latest[4] or 0}m, 흘수={latest[5] or 0}m')
        lines.append(f'- 목적지: {latest[6] or "N/A"}')
        lines.append(f'- 상태: {latest[7] or "N/A"}')
        lines.append(f'- 호출부호: {latest[8] or "N/A"}, IMO: {latest[9] or "N/A"}')

        # 변경 이력 감지
        changes = []
        for i in range(len(rows) - 1):
            curr, prev = rows[i], rows[i + 1]
            diffs = []
            if curr[1] != prev[1]:
                diffs.append(f'선명: {prev[1]}→{curr[1]}')
            if curr[6] != prev[6]:
                diffs.append(f'목적지: {prev[6]}→{curr[6]}')
            if curr[7] != prev[7]:
                diffs.append(f'상태: {prev[7]}→{curr[7]}')
            if diffs:
                changes.append(f'- {curr[0].strftime("%m/%d %H:%M")}: {", ".join(diffs)}')

        if changes:
            lines.append(f'\n### 변경 이력 (최근 {len(changes)}건)')
            lines.extend(changes[:10])

        return '\n'.join(lines)
    except Exception as e:
        logger.error('vessel static query failed: %s', e)
        return f'\n(정적정보 조회 실패: {e})\n'


def _query_gear_correlation(params: dict) -> str:
    """어구 그룹의 연관 선박/어구 조회."""
    from db import kcgdb

    group_key = params.get('group_key', '')
    limit = int(params.get('limit', 10))

    with kcgdb.get_conn() as conn:
        cur = conn.cursor()
        try:
            cur.execute(
                'SELECT target_name, target_mmsi, target_type, current_score, '
                'streak_count, observation_count, proximity_ratio, visit_score, '
                'heading_coherence, freeze_state '
                f'FROM {GEAR_CORRELATION_SCORES} s '
                f'JOIN {CORRELATION_PARAM_MODELS} m ON s.model_id = m.id '
                'WHERE s.group_key = %s AND m.is_default = TRUE AND s.current_score >= 0.3 '
                'ORDER BY s.current_score DESC LIMIT %s',
                (group_key, limit),
            )
            rows = cur.fetchall()
        except Exception:
            return f'어구 그룹 "{group_key}"에 대한 연관성 데이터가 없습니다 (테이블 미생성).'
        finally:
            cur.close()

    if not rows:
        return f'어구 그룹 "{group_key}"에 대한 연관성 데이터가 없습니다.'

    lines = [f'## {group_key} 연관 분석 (상위 {len(rows)}개, default 모델)']
    for r in rows:
        name, mmsi, ttype, score, streak, obs, prox, visit, heading, state = r
        pct = score * 100
        disp_name = name or mmsi
        detail_parts = []
        if prox is not None:
            detail_parts.append(f'근접 {prox*100:.0f}%')
        if visit is not None:
            detail_parts.append(f'방문 {visit*100:.0f}%')
        if heading is not None:
            detail_parts.append(f'COG동조 {heading*100:.0f}%')
        detail = ', '.join(detail_parts) if detail_parts else ''

        lines.append(
            f'- **{disp_name}** ({mmsi}, {ttype}): '
            f'일치율 {pct:.1f}% (연속 {streak}회, 관측 {obs}회) '
            f'[{detail}] 상태: {state}'
        )
    return '\n'.join(lines)
