#!/usr/bin/env python3
"""
RAG search engine v2: uses optimized pickle index
Loads in ~6s instead of ~50s, uses 96% less disk
"""
import json
import os
import re
import math
import pickle
import struct
from collections import Counter
from pathlib import Path


try:
    from config import cfg as _app_cfg
    _HAS_CONFIG = True
except ImportError:
    _HAS_CONFIG = False

_index_cache = {}


def _get_config():
    if _HAS_CONFIG:
        return {
            'base_dir': _app_cfg.base_dir,
            'index_file': _app_cfg.index_file,
            'keyword_file': _app_cfg.keyword_file,
        }
    # Fallback: environment variable
    base = Path(os.environ.get('KNOWLEDGE_DIR', str(Path(__file__).parent)))
    return {
        'base_dir': base,
        'index_file': base / 'rag_index.json',
        'keyword_file': base / 'rag_index.keywords.json',
    }


def _load_index():
    if 'chunks' not in _index_cache:
        cfg = _get_config()
        base = cfg['base_dir']

        # Try optimized pickle first, fall back to JSON
        pkl_index = base / 'rag_index.pkl'
        pkl_kw = base / 'rag_keywords.pkl'

        if pkl_index.exists() and pkl_kw.exists():
            with open(pkl_index, 'rb') as f:
                data = pickle.load(f)
            with open(pkl_kw, 'rb') as f:
                kw_raw = pickle.load(f)

            chunks = data['chunks']  # list of (source, chunk_id, text) tuples
            use_bytes = data.get('use_bytes', False)

            # Build lookup
            chunk_lookup = {}
            for i, (source, chunk_id, text) in enumerate(chunks):
                cid = f"{source}#chunk{chunk_id}"
                chunk_lookup[i] = {
                    'id': cid, 'source': source,
                    'chunk_id': chunk_id, 'text': text, 'length': len(text),
                }

            # Convert bytes postings back to int lists on demand
            _index_cache['chunks'] = chunk_lookup
            _index_cache['total'] = len(chunks)
            _index_cache['keywords'] = kw_raw
            _index_cache['use_bytes'] = use_bytes
        else:
            # Fallback to JSON
            json_index = base / 'rag_index.json'
            json_kw = base / 'rag_index.keywords.json'
            with open(json_index, 'r', encoding='utf-8') as f:
                data = json.load(f)
            _index_cache['chunks'] = {c['id']: c for c in data['chunks']}
            _index_cache['total'] = data['total_chunks']
            with open(json_kw, 'r', encoding='utf-8') as f:
                _index_cache['keywords'] = json.load(f)
            _index_cache['use_bytes'] = False

    return _index_cache


def _decode_postings(raw, use_bytes):
    """Decode posting list from bytes or list"""
    if use_bytes and isinstance(raw, bytes):
        n = len(raw) // 2
        return list(struct.unpack(f"<{n}H", raw))
    return raw


def tokenize(text):
    text = text.lower()
    words = []
    chinese = re.findall(r'[\u4e00-\u9fff]+', text)
    for cc in chinese:
        for i in range(len(cc) - 1):
            words.append(cc[i:i+2])
        if len(cc) >= 3:
            for i in range(len(cc) - 2):
                words.append(cc[i:i+3])
    words.extend(re.findall(r'[a-z]+', text))
    return words


def search(query, top_k=5):
    idx = _load_index()
    keywords = idx['keywords']
    chunks = idx['chunks']
    total = idx['total']
    use_bytes = idx.get('use_bytes', False)

    query_tokens = tokenize(query)
    if not query_tokens:
        return []

    scores = Counter()
    for token in query_tokens:
        if token not in keywords:
            continue
        posting_raw = keywords[token]
        posting_list = _decode_postings(posting_raw, use_bytes)
        idf = math.log((total - len(posting_list) + 0.5) / (len(posting_list) + 0.5) + 1)
        for idx_num in posting_list:
            scores[idx_num] += idf

    top = scores.most_common(top_k)
    results = []
    for idx_num, score in top:
        chunk = chunks.get(idx_num)
        if chunk:
            results.append({
                'source': chunk['source'],
                'chunk_id': chunk['chunk_id'],
                'score': round(score, 3),
                'text': chunk['text'],
            })
    return results


if __name__ == '__main__':
    import sys, time
    cfg = _get_config()
    print(f'Config: base={cfg["base_dir"]}')

    t0 = time.time()
    _load_index()
    t1 = time.time()
    print(f'Index loaded in {t1-t0:.1f}s')

    query = sys.argv[1] if len(sys.argv) > 1 else '船舶进出港报告'
    t0 = time.time()
    results = search(query, top_k=3)
    t1 = time.time()
    print(f'Search "{query}" in {(t1-t0)*1000:.0f}ms')
    for i, r in enumerate(results):
        print(f'\n[{i+1}] {r["source"][:50]} (score={r["score"]})')
        print(f'    {r["text"][:150]}...')
