import json
import csv
import os
import re
from dataclasses import asdict
from typing import Optional
from mando import command, arg
from rich.console import Console
from jinja2 import Template  # 用于按需渲染模板
    

from ketacli.sdk.convert_ir.spl_parser import parse_spl
from ketacli.sdk.convert_ir.zabbix_lexparser import parse_zabbix_lex as parse_zabbix 
from ketacli.sdk.convert_ir.spl_generator import to_spl

console = Console()


def _read_text(text: Optional[str], file: Optional[str]) -> Optional[str]:
    if text and text.strip():
        return text
    if file:
        try:
            with open(file, 'r', encoding='utf-8') as f:
                return f.read()
        except Exception as e:
            console.print(f"[red]读取文件失败: {e}[/red]")
            return None
    console.print("[red]请通过 -t/--text 或 -f/--file 提供输入文本[/red]")
    return None


def _write_output(content: str, output: Optional[str]) -> None:
    if output:
        try:
            with open(output, 'w', encoding='utf-8') as f:
                f.write(content)
            console.print(f"[green]已写入输出文件: {output}[/green]")
        except Exception as e:
            console.print(f"[red]写入输出失败: {e}[/red]")
    else:
        console.print(content, overflow="fold", markup=False)


@command
@arg('text', '-t', '--text', type=str)
@arg('file', '-f', '--file', type=str)
@arg('csv_file', '--csv', type=str)
@arg('output', '-o', '--output', type=str)
@arg('fmt', '--fmt', type=str,
     completer=lambda prefix, **kwd: [x for x in ['json', 'repr'] if x.startswith(prefix)])
@arg('src', '--src', type=str,
     completer=lambda prefix, **kwd: [x for x in ['spl', 'zabbix'] if x.startswith(prefix)])
@arg('dst', '--dst', type=str,
     completer=lambda prefix, **kwd: [x for x in ['ir', 'spl'] if x.startswith(prefix)])
@arg('repo', '--repo', type=str)
@arg('file_field', '--file-field', type=str)
@arg('name_field', '--name-field', type=str)
@arg('zabbix_field', '--zabbix-field', type=str)
@arg('alert_template', '--alert-template', type=str)
@arg('template_vars', '--template-vars', type=str)
def convert(text=None, file=None, csv_file=None, output=None, fmt="json", src=None, dst=None, repo="zabbix", extra:str="", name_field:Optional[str]=None, zabbix_field:Optional[str]=None, alert_template:Optional[str]=None, template_vars:Optional[str]=None):
    """语法转换工具

    :param -t, --text: 输入文本（随 src 指定的语法解析）
    :param -f, --file: 从文件读取输入文本
    :param --csv: 从 CSV 文件批量转换，每行生成一个告警模板 JSON
    :param -o, --output: 输出到指定文件（不传则打印）
    :param --fmt: 当 dst=ir 时的输出格式：json | repr（默认 json）
    :param --src: 原始语法（当前支持：spl | zabbix）
    :param --dst: 目标语法（当前支持：ir | spl）
    :param --repo: 非 spl 源语法时的默认数据源（repo），覆盖解析器默认值
    :param --extra: 额外参数，格式为 key1=value1,key2=value2（当前仅支持 Zabbix 日志触发器中的文件来源字段名 file_field）
    :param --name-field: 当 --csv 时，指定列名作为告警规则名称
    :param --zabbix-field: 当 --csv 且 --src=zabbix 时，指定列名作为 Zabbix 规则表达式
    :param --template-vars: 通过 key=value,key2=value2 传入 Jinja2 渲染参数，填充模板中的 {{ }} 变量
    :param --alert-template: 告警模板 JSON 文件路径，默认使用 sdk/convert_ir/templates/alert_template.json
    
    """
    def _parse_kv_pairs(pairs: Optional[str]) -> dict:
        if not pairs:
            return {}
        items = {}
        for seg in pairs.split(','):
            seg = seg.strip()
            if not seg:
                continue
            if '=' not in seg:
                # 支持仅提供 key，值置为空字符串
                items[seg] = ""
                continue
            k, v = seg.split('=', 1)
            items[k.strip()] = v
        return items
    def _sanitize_alert_name(name: str) -> str:
        # 将不允许的字符替换为下划线，并压缩连续下划线
        if not isinstance(name, str):
            name = str(name)
        cleaned = re.sub(r'["\'\*\?]', '_', name)
        cleaned = re.sub(r'_+', '_', cleaned).strip()
        return cleaned
    # 批量 CSV 转换路径
    if csv_file or (file and file.lower().endswith('.csv')):
        csv_path = csv_file or file
        try:
            with open(csv_path, 'r', encoding='utf-8') as f:
                reader = csv.DictReader(f)
                rows = list(reader)
        except Exception as e:
            console.print(f"[red]读取 CSV 失败: {e}[/red]")
            return

        if not rows:
            console.print("[yellow]CSV 空文件，无数据可转换[/yellow]")
            return

        # 选择模板路径
        default_tpl = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'sdk', 'convert_ir', 'templates', 'alert_template.json')
        tpl_path = alert_template or default_tpl
        # 加载模板文本/对象
        tpl_obj = None
        tpl_text = None
        try:
            with open(tpl_path, 'r', encoding='utf-8') as tf:
                tpl_text = tf.read()
                # 若后续不进行 Jinja 渲染，作为 JSON 直接解析
                try:
                    tpl_obj = json.loads(tpl_text)
                except Exception:
                    tpl_obj = None
        except Exception as e:
            console.print(f"[red]加载告警模板失败: {e}[/red]")
            return

        # 批量构建输出
        out_lines = []
        idx = 0
        for row in rows:
            idx += 1
            # 告警名称
            alert_name = None
            if name_field:
                alert_name = row.get(name_field)
            else:
                # 兜底：尝试常见列名
                for candidate in ['name', 'alert_name', 'rule_name']:
                    if candidate in row:
                        alert_name = row.get(candidate)
                        break
            if not alert_name:
                alert_name = f"rule_{idx}"
            else:
                alert_name = _sanitize_alert_name(alert_name)

            # 原始表达式（zabbix 或 spl）
            expr_text = None
            if zabbix_field:
                expr_text = row.get(zabbix_field)
            else:
                for candidate in ['original', 'zabbix', 'expr', 'expression', 'spl']:
                    if candidate in row:
                        expr_text = row.get(candidate)
                        break
            if not expr_text or not str(expr_text).strip():
                console.print(f"[yellow]第{idx}行缺少表达式，跳过[/yellow]")
                continue

            # 解析生成 SPL
            try:
                if src == 'zabbix':
                    pipeline = parse_zabbix(expr_text, default_repo=repo)
                    if repo:
                        try:
                            from ketacli.sdk.convert_ir.ir import SearchStage
                            for st in pipeline.stages:
                                if isinstance(st, SearchStage):
                                    st.repo = repo
                        except Exception:
                            pass
                    spl_text = to_spl(pipeline)
                elif src == 'spl':
                    # 直接格式化/规范化 SPL
                    pipeline = parse_spl(expr_text)
                    spl_text = to_spl(pipeline)
                else:
                    console.print(f"[red]不支持的 src: {src}[/red]")
                    return
            except Exception as e:
                console.print(f"[red]第{idx}行转换失败: {e}[/red]")
                continue

            # 构建告警 JSON（每行一个独立对象）
            try:

                # 规范默认模板中的四重花括号为 Jinja 变量
                normalized_tpl = tpl_text.replace('{{{{ alert_name }}}}', '{{ alert_name }}')
                # 将模板中可能导致 JSON 引号逃逸的问题替换为无引号占位符，配合 *_json 变量
                normalized_tpl = normalized_tpl.replace('"spl": "{{ spl }}"', '"spl": {{ spl_json }}')
                normalized_tpl = normalized_tpl.replace('"name": "{{ alert_name }}"', '"name": {{ alert_name_json }}')
                # 构建变量映射，允许用户覆盖
                mapping = {'alert_name': alert_name, 'spl': spl_text}
                user_vars = _parse_kv_pairs(template_vars)
                mapping.update(user_vars)
                # 始终对 alert_name 做合法字符清洗（允许用户覆盖后再清洗）
                mapping['alert_name'] = _sanitize_alert_name(mapping.get('alert_name', alert_name))
                # 供模板安全插入 JSON 字符串字面量
                mapping['spl_json'] = json.dumps(mapping.get('spl', spl_text), ensure_ascii=False)
                mapping['alert_name_json'] = json.dumps(mapping.get('alert_name', alert_name), ensure_ascii=False)
                rendered = Template(normalized_tpl).render(**mapping)
                alert_obj = json.loads(rendered)
                

                out_lines.append(json.dumps(alert_obj, ensure_ascii=False))
            except Exception as e:
                console.print(f"[red]第{idx}行生成告警 JSON 失败: {e}[/red]")
                continue

        # 输出换行分隔 JSON
        final_text = "\n".join(out_lines)
        _write_output(final_text, output)
        return

    # 单条文本转换路径
    input_text = _read_text(text, file)
    if not input_text:
        return

    supported_src = ['spl', 'zabbix']
    supported_dst = ['ir', 'spl']

    if extra:
        extra_params = dict(pair.split('=', 1) for pair in extra.split(','))
    else:
        extra_params = {}

    try:
        if not src or not dst:
            console.print("[red]请同时指定 --src 和 --dst[/red]")
            return
        if src not in supported_src:
            console.print(f"[red]不支持的 src: {src}，可选: {', '.join(supported_src)}[/red]")
            return
        if dst not in supported_dst:
            console.print(f"[red]不支持的 dst: {dst}，可选: {', '.join(supported_dst)}[/red]")
            return

        # 解析为 IR Pipeline
        if src == 'spl':
            pipeline = parse_spl(input_text)
        elif src == 'zabbix':
            # 解析器不再区分 metrics/events repo，统一由用户传入的 --repo 覆盖
            pipeline = parse_zabbix(input_text, default_repo=repo, **extra_params)
            # 覆盖所有 SearchStage 的 repo 为用户传入（如果提供）
            if repo:
                try:
                    from ketacli.sdk.convert_ir.ir import SearchStage
                    for st in pipeline.stages:
                        if isinstance(st, SearchStage):
                            st.repo = repo
                except Exception:
                    pass
        # 生成目标语法
        if dst == 'spl':
            normalized = to_spl(pipeline)
            _write_output(normalized, output)
        elif dst == 'ir':
            if fmt == 'repr':
                _write_output(repr(pipeline), output)
            else:
                payload = json.dumps(asdict(pipeline), ensure_ascii=False, indent=2)
                _write_output(payload, output)
    except Exception as e:
        console.print(f"[red]转换失败: {e}[/red]")