import requests
import nltk
import nltk.data
import re
from nltk.tokenize import sent_tokenize
import json
import time
import ast
from colorama import Fore, Style, init
import textwrap
init(autoreset=True)
from .cache import ensure_cache, cache_get, cache_set
import sys

# ---- 尝试加载 punkt ----
try:
    nltk.data.find("tokenizers/punkt")
    nltk.data.find("tokenizers/punkt_tab")
except LookupError:
    nltk.download("punkt")
    nltk.download("punkt_tab")

# 你的 Vercel 代理 API
HF_API_URL = "https://goodgute.vercel.app/api/translate"
PPLX_API_URL = "https://goodgute.vercel.app/api/qe"

# chunk token approx size
MAX_CHARS = 1250


CACHE_TABLE_CREATED = False

CACHE_ENABLED = False
VERBOSE_ENABLED = True
DEV_ENABLED = False
PPLX_SUCCESS = True

def print_translation_header():
    border = "═" * 50
    print(f"\n\n{Fore.CYAN}{Style.BRIGHT}╔{border}╗")
    print(f"║{Fore.CYAN}{Style.BRIGHT}{'= = = Translation Result = = ='.center(50)}{Fore.CYAN}║")
    print(f"{Fore.CYAN}╚{border}╝\n\n")

def chunk_text(text):
    """按句子进行 chunking"""
    sentences = safe_sent_tokenize(text)
    chunks = []
    current = ""

    for s in sentences:
        if len(current) + len(s) < MAX_CHARS:
            current += " " + s
        else:
            chunks.append(current.strip())
            current = s

    if current.strip():
        chunks.append(current.strip())

    return chunks

def safe_sent_tokenize(text):
    try:
        return sent_tokenize(text)
    except Exception as e:
       #logger.warning(f"sent_tokenize failed, fallback: {e}")

        return re.split(r'(?<=[.!?])\s+', text)


def call_HF_api(text, reverse_sign=False):

    global VERBOSE_ENABLED
    try:
        resp = requests.post(
            HF_API_URL,
            json={"text": text, "reverse": reverse_sign},
            timeout=120,
        )
        #if VERBOSE_ENABLED:
            #print(f"\n\n{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Sending to HuggingFace API for translation...\n\n")
        resp.raise_for_status()
        data = resp.json()


        if isinstance(data, dict) and "result" in data:
            return str(data["result"])


        if isinstance(data, list) and len(data) > 0:
            item = data[0]
            if isinstance(item, dict) and "translation_text" in item:
                return str(item["translation_text"])


        if isinstance(data, dict):
            return str(data)


        return str(data)

    except Exception as e:
        pass
    

def process_hf_response(resp):
    """
    Handle HuggingFace API response that can be:
    - a dict string (with 'error')
    - a plain translation string
    """
    # Try to parse as dict
    if isinstance(resp, str):
        try:
            # safe literal eval, turns "{'error': '...'}" into dict
            resp_parsed = ast.literal_eval(resp)
        except (ValueError, SyntaxError):
            # not a dict string → treat as valid translation
            return resp

        if isinstance(resp_parsed, dict) and "error" in resp_parsed:
            # endpoint not ready
            return resp_parsed
        else:
            # somehow parsed but not error → treat as string
            return str(resp_parsed)

    # Already dict or something else
    if isinstance(resp, dict) and "error" in resp:
        return resp

    # fallback → return string
    return str(resp)

def call_with_retry(RETRY_DELAY:list, MAX_RETRIES=3):
    global DEV_ENABLED
    for attempt in range(1, MAX_RETRIES + 1):
        result = call_HF_api("_temp_text")
        result = process_hf_response(result)
        
        if isinstance(result, dict) and "error" in result:
            error_msg = str(result["error"]).lower()
            
            if "paused" in error_msg or "503" in error_msg:
                print(f"\n\n{Fore.RED}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] API not ready ({error_msg}). Retry {attempt}/{MAX_RETRIES} in {RETRY_DELAY[attempt-1]}s...\n\n")
                if attempt < MAX_RETRIES:
                    time.sleep(RETRY_DELAY[attempt-1])
                    continue
                else:
                    if DEV_ENABLED:
                    
                        raise RuntimeError(f"API failed after {MAX_RETRIES} retries: {error_msg}")
                    else:
                        print(f"\n\n{Fore.RED}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] API failed after {MAX_RETRIES} retries: {error_msg}")
                        sys.exit(1)
            else:
                if DEV_ENABLED:

                    raise RuntimeError(f"API returned error: {error_msg}")
                else:
                    print(f"\n\n{Fore.RED}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] API returned error: {error_msg}")
                    sys.exit(1)



def translate_with_cache(text: str, reverse_sign=False):


    global CACHE_ENABLED
    global VERBOSE_ENABLED

    api_req_calls = 0
    num_cache_hit = 0
    num_cache_miss = 0
    delay_time = [10, 15, 25]
    cache_hit = False
    if CACHE_ENABLED:
        cached = cache_get(text)
        if cached is not None:
            if VERBOSE_ENABLED:
                cache_hit = True
                num_cache_hit +=1
                print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Cache[{Fore.GREEN}{Style.BRIGHT}HIT{Style.RESET_ALL}] Using local cache...")
            return cached, cache_hit, num_cache_hit, num_cache_miss, api_req_calls


        if VERBOSE_ENABLED:
            print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Cache[{Fore.RED}{Style.BRIGHT}MISS{Style.RESET_ALL}] Calling HuggingFace API to translate...")
            
    call_with_retry(RETRY_DELAY=delay_time)
    result = call_HF_api(text, reverse_sign)
    api_req_calls +=1

    processed = extract_translation_text(result)


    if CACHE_ENABLED:
        cache_set(text, processed)
    cache_hit = False
    num_cache_miss +=1
    return processed, cache_hit, num_cache_hit, num_cache_miss, api_req_calls


def translate_text(text, reverse_sign=False):

    global VERBOSE_ENABLED
    
    cache_hit = False
    if len(text) < MAX_CHARS:
        t, cache_hit, num_cache_hit, num_cache_miss, api_req_calls = translate_with_cache(text)
        return t, cache_hit, num_cache_hit, num_cache_miss, api_req_calls

    chunks = chunk_text(text)
    results = []
    sub_num_cache_hit = 0
    sub_num_cache_miss = 0
    sub_api_req_calls = 0
    for i, chunk in enumerate(chunks, 1):
        
        t, cache_hit, num_cache_hit, num_cache_miss, api_req_calls = translate_with_cache(chunk)
        sub_api_req_calls += api_req_calls
        sub_num_cache_hit += num_cache_hit
        sub_num_cache_miss += num_cache_miss
        results.append(t)

    joined_results = "\n".join(results)
    return joined_results, cache_hit, sub_num_cache_hit, sub_num_cache_miss, sub_api_req_calls

def extract_translation_text(output):


    if isinstance(output, list) and len(output) > 0:
        item = output[0]
        if isinstance(item, dict) and "translation_text" in item:
            return item["translation_text"]


    return str(output)



def split_into_paragraphs(text):
    

    paras = [p.strip() for p in text.split("\n\n") if p.strip()]
    return paras


def is_structured_line(line):

    stripped = line.lstrip()

    

    # markdown 标题
    if re.match(r"^#{1,6}\s+", stripped):
        return True

    # 列表（无序）
    if re.match(r"^(\*|\-|\+)\s+", stripped):
        return True

    # 列表（有序 1. 1) 2. 2)）
    if re.match(r"^\d+[\.\)]\s+", stripped):
        return True

    # bulletpoint
    if re.match(r"^[•‣▪◦●]\s+", stripped):
        return True
    
    # blockquote
    if re.match(r"^>\s+", stripped):
        return True

    # fenced code block: ``` 或 ~~~
    if re.match(r"^(```|~~~)", stripped):
        return True

    # 任务列表 - [ ]
    if re.match(r"^(\-|\*|\+) \[.\]\s+", stripped):
        return True

    # 表格行 | col | col |
    if stripped.startswith("|") and stripped.endswith("|"):
        return True
    
    words = line.split()
    if 1 <= len(words) <=10:
        return True
    return False


def smart_split_paragraphs(text):

    lines = text.split("\n")
    blocks = []
    current = []
    
    for line in lines:
        stripped = line.strip()

        # 1) 空行：结束当前段落
        if stripped == "":
            if current:
                blocks.append("\n".join(current))
                current = []
            blocks.append("")  # 保留空行
            continue

        # 2) 结构行：单独成段
        if is_structured_line(stripped):
            if current:
                blocks.append("\n".join(current))
                current = []
            blocks.append(stripped)
            
            continue

        if current and re.match(r".*[\.\?\!]$", current[-1].strip()):
            blocks.append("\n".join(current).strip())
            current = []


        # 3) 普通行：加入当前段落（保持结构）
        current.append(line)

    # 收尾
    if current:
        blocks.append("\n".join(current))
        
    return blocks

def text_stat_before(text):
    words_count_before = len(text.strip())
    blocks = smart_split_paragraphs(text)
    para_num = sum(1 for b in blocks if b.strip())
    chunk_num = 0
    for i, block in enumerate(blocks, 1):
     
        # 如果是空段落
        if block.strip() == "":
            continue

        # 如果是结构行（如 # / - / 1.）
        if is_structured_line(block):
            # 不翻译结构符号，但是翻译内容
            # e.g. "# Title here" → "# 这里是标题"
            parts = block.split(maxsplit=1)
            if len(parts) == 1:
                pass
                
            else:
                parts = block.split(maxsplit=1)
                if len(parts) < MAX_CHARS:
                    chunk_num +=1
                  
                else:
                    symbol, content = parts[0], parts[1]
                    chunks = chunk_text(content)
                    chunk_num += len(chunks)
                    
            continue

        if len(block) < MAX_CHARS:
            chunk_num +=1
           
        else:
            chunks = chunk_text(block)
            chunk_num += len(chunks)
            

    # return  para_num, words_count_before, chunk_num  ESTIMATED_HF_API_CALLING_TIMES, MAX_CHARS

    return para_num, words_count_before, chunk_num, chunk_num, MAX_CHARS


def translate_preserve_paragraphs(text, reverse_sign=False):

    global CACHE_TABLE_CREATED
    global CACHE_ENABLED
    global VERBOSE_ENABLED

    
    words_count_before = len(text.strip())
    if CACHE_ENABLED:
        if not CACHE_TABLE_CREATED:
            ensure_cache()
            CACHE_TABLE_CREATED = True
    blocks = smart_split_paragraphs(text)
    para_num = sum(1 for b in blocks if b.strip())
    results = []
    para_idx = 0
    total_num_cache_hit = 0
    total_num_cache_miss = 0
    total_api_req_calls = 0
    print("\n")
    for i, block in enumerate(blocks, 1):

        # 如果是空段落
        if block.strip() == "":
            results.append("")
            continue

        # 如果是结构行（如 # / - / 1.）
        if is_structured_line(block):
            # 不翻译结构符号，但是翻译内容
            # e.g. "# Title here" → "# 这里是标题"
            parts = block.split(maxsplit=1)
            if len(parts) == 1:
                
                results.append(block)  # 没内容，不翻译
            else:
                symbol, content = parts[0], parts[1]
                para_idx += 1
                if VERBOSE_ENABLED:
                    print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Start translating paras {Fore.RED}{Style.BRIGHT}{para_idx}{Style.RESET_ALL}/{Fore.GREEN}{Style.BRIGHT}{para_num}{Style.RESET_ALL}...")
        
                translated, cache_hit, num_cache_hit, num_cache_miss, api_req_calls = translate_text(content, reverse_sign)
                total_api_req_calls += api_req_calls
                total_num_cache_hit += num_cache_hit
                total_num_cache_miss += num_cache_miss

                if VERBOSE_ENABLED:
                    if cache_hit:
                        print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Start using [{Fore.GREEN}{Style.BRIGHT}Local Cache{Style.RESET_ALL}] translating paras {Fore.RED}{Style.BRIGHT}{para_idx}{Style.RESET_ALL}/{Fore.GREEN}{Style.BRIGHT}{para_num}{Style.RESET_ALL}...")
                    elif not cache_hit:
                        print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Start using [{Fore.GREEN}{Style.BRIGHT}HuggingFace API{Style.RESET_ALL}] translating paras {Fore.RED}{Style.BRIGHT}{para_idx}{Style.RESET_ALL}/{Fore.GREEN}{Style.BRIGHT}{para_num}{Style.RESET_ALL}...")

                results.append(f"{symbol} {translated}")
                
            continue
        # 普通段落 → 翻译
        
        para_idx += 1
        if VERBOSE_ENABLED:
            print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Start translating paras {Fore.RED}{Style.BRIGHT}{para_idx}{Style.RESET_ALL}/{Fore.GREEN}{Style.BRIGHT}{para_num}{Style.RESET_ALL}...")
        translated, cache_hit, num_cache_hit, num_cache_miss, api_req_calls = translate_text(block, reverse_sign)
        total_api_req_calls += api_req_calls
        total_num_cache_hit += num_cache_hit
        total_num_cache_miss += num_cache_miss

        if VERBOSE_ENABLED:
            if cache_hit:
                print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Start using [{Fore.GREEN}{Style.BRIGHT}Local Cache{Style.RESET_ALL}] translating paras {Fore.RED}{Style.BRIGHT}{para_idx}{Style.RESET_ALL}/{Fore.GREEN}{Style.BRIGHT}{para_num}{Style.RESET_ALL}...")
            elif not cache_hit:
                print(f"{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Start using [{Fore.GREEN}{Style.BRIGHT}HuggingFace API{Style.RESET_ALL}] translating paras {Fore.RED}{Style.BRIGHT}{para_idx}{Style.RESET_ALL}/{Fore.GREEN}{Style.BRIGHT}{para_num}{Style.RESET_ALL}...")

        results.append(translated)

    # return results, para_num, words_count_before, words_count_after, HF_API_CALLING_TIMES, num_cache_hit，num_cache_miss
    final_results = "\n".join(results)
    words_count_after = len(final_results.strip())
    return final_results, para_num, words_count_before, words_count_after, total_api_req_calls, total_num_cache_hit, total_num_cache_miss

def quality_evaluate(source: str, translation: str) -> str:

    global VERBOSE_ENABLED
    global DEV_ENABLED
    if VERBOSE_ENABLED:
        print(f"\n\n{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] Sending to Perplexity API for Translation Quality Evaluation...\n\n")
    payload = {
        "source": source,
        "translation": translation
    }

    resp = requests.post(PPLX_API_URL, json=payload)
    resp.raise_for_status()
    data = resp.json()

    if DEV_ENABLED:
        eval = data.get("evaluation","")
        if not eval:
            print(f"\n\n{Fore.GREEN}{Style.BRIGHT}>{Style.RESET_ALL} [GoodGute] [{Fore.RED}{Style.BRIGHT}Error{Style.RESET_ALL}] Pplx json value retured is None, NO Eval...\n\n")
            sys.exit(1)
    return data.get("evaluation", "")




# 颜色定义
LEVEL_COLOR = {
    "excellent": Fore.GREEN + Style.BRIGHT,
    "good": Fore.GREEN,
    "okay": Fore.YELLOW,
    "Poor": Fore.RED,
    "Very poor": Fore.RED + Style.BRIGHT,
}

# 星级映射
LEVEL_STARS = {
    "excellent": 5,
    "good": 4,
    "okay": 3,
    "Poor": 2,
    "Very poor": 1,
}

def make_stars(level: str):
    """根据评分等级生成星星字符串"""
    lv = level.lower().strip()
    stars = LEVEL_STARS.get(lv, 0)

    solid = f"{Fore.GREEN}{Style.BRIGHT}✓{Style.RESET_ALL}" * stars
    empty = f"{Style.BRIGHT}⨉" * (5 - stars)
    return solid + empty


def colorize_level(level: str):
    """给评分等级加颜色"""
    key = level.lower().strip()
    color = LEVEL_COLOR.get(key, Fore.WHITE)
    return f"{color}{level}{Style.RESET_ALL}"


def wrap_lines(text, width=70):
    return textwrap.wrap(text, width=width)


ANSI_ESCAPE = re.compile(r'\x1b\[[0-9;]*m')

def strip_ansi(s: str) -> str:
    """Remove ANSI escape sequences from a string."""
    return ANSI_ESCAPE.sub('', s or '')

def visible_len(s: str) -> int:
    """
    Return the number of terminal column cells the string will occupy.
    Uses wcwidth if available (handles emoji / CJK widths); otherwise falls back to len() on stripped string.
    """
    
    s_clean = strip_ansi(s)
    return len(s_clean)


def render_qe_line(key: str, colored_value: str, stars: str, key_pad: int = 28, val_pad: int = 15):
    """
    Print a single row like:
    • Term accuracy             : good        ✓✓✓✓⨉

    Arguments:
      key         - metric name (no color)
      colored_value - colored value string (may contain ANSI codes)
      stars       - star string (emoji or ticks)
      key_pad     - target width for key column (visible columns)
      val_pad     - target width for value column (visible columns)
    """
    # prepare visible strings (color retained in colored_value)
    key_display = (key.capitalize() or "").strip()
    # compute visible lengths
    key_vis_len = visible_len(key_display)
    val_vis_len = visible_len(colored_value)

    # compute paddings needed (based on visible width)
    key_space = max(1, key_pad - key_vis_len)
    val_space = max(1, val_pad - val_vis_len)

    # build padded pieces (we add spaces *after* the colored_value, not inside it)
    key_piece = key_display + (" " * key_space)
    value_piece = colored_value + (" " * val_space)

    print(f"    • {key_piece} : {value_piece} {stars}")



def infer_rating_from_text(text: str) -> str:


    t = text.lower()


    if "excellent" in t:
        return "excellent"
    if "good" in t:
        return "good"
    return "okay"

def render_qe_output(result_json: dict):
    """
    美观渲染 Perplexity 翻译质量检测结果 JSON
    """

    title_color = Fore.CYAN + Style.BRIGHT
    border = "═" * 72

    print(f"\n{title_color}╔{border}╗")
    print(f"{title_color}║{' Translation Quality Evaluation ':+^72}║")
    print(f"{title_color}╠{border}╣")

    print("\n")
    print(Fore.WHITE + Style.BRIGHT + "  Quality Dimensions (with Ratings):\n")

    # 需要排除的文本字段
    text_fields = {"improvement recommendations", "overall comments"}

    for key, value in result_json.items():
        if key in text_fields:
            continue
        
        if not isinstance(value,str):
            value = str(value)
        
        value = infer_rating_from_text(value)
        colored_value = colorize_level(value)
        stars = make_stars(value)
        #print(f"    • {key.capitalize():30} : {colored_value:15} {stars}")
        render_qe_line(key, colored_value, stars)

    print(f"{title_color}╠{border}╣")

    # Improvement Recommendations
    if "improvement recommendations" in result_json:
        print(Fore.WHITE + Style.BRIGHT + "\n  Improvement Recommendations:\n")
        
        # 原文按空行或数字列表分段
        raw_text = result_json["improvement recommendations"]
        if not isinstance(raw_text, str):
            # 如果是 dict / None / 其他 → 转成字符串
            raw_text = json.dumps(raw_text, ensure_ascii=False)
        # 先按数字或空行拆段
        paragraphs = re.split(r'(?=\d+\.\s)', raw_text)

  
        for i, para in enumerate(paragraphs):

            para = para.strip()
            if not para:
                print("")  # 保留空行
                continue
            
            for line in wrap_lines(para, width=66):  # 可以设置wrap宽度
                print(f"    {line}")
            if i < len(paragraphs) - 1:
                print("")  
            

    print(f"{title_color}╠{border}╣")

    # Overall Comments
    if "overall comments" in result_json:
        print(Fore.WHITE + Style.BRIGHT + "\n  Overall Comments:\n")
        raw_text_comms = result_json["overall comments"]
        if not isinstance(raw_text_comms, str):
            # 如果是 dict / None / 其他 → 转成字符串
            raw_text_comms = json.dumps(raw_text_comms, ensure_ascii=False)
        for line in wrap_lines(raw_text_comms, width=66):
            print(f"    {line}")

    print(f"{title_color}╚{border}╝\n")


def safe_json_loads(raw: str):

    global DEV_ENABLED
    global PPLX_SUCCESS
    s = raw.strip()

    # 如果是 Python dict 风格（单引号），转换成双引号 JSON 风格
    if s.startswith("{") and "'" in s and '"' not in s:
        s = s.replace("'", '"')

    # 去除 Markdown 包裹 ```json ... ```
    s = re.sub(r"^```json|```$", "", s, flags=re.IGNORECASE).strip()
    s = re.sub(r"^```|```$", "", s).strip()

    # 去除三引号包裹 """ ... """
    if s.startswith('"""') and s.endswith('"""'):
        s = s[3:-3].strip()

    # 去除 BOM 头
    s = s.lstrip("\ufeff")

    # 最终尝试 json.loads
    try:
        PPLX_SUCCESS = True
        return json.loads(s)
    except Exception as e:
        PPLX_SUCCESS = False
        if DEV_ENABLED:
            raise RuntimeError(f"PPLX_REQ_RETURN_FORMAT_ERROR: {e}")
        elif not DEV_ENABLED:
            pass