from datetime import datetime, timezone
from typing import Any

from app.config import security_config
from app.security.input_guard import process_input
from app.security.jailbreak_guard import detect_jailbreak
from app.security.output_guard import filter_output
from app.security.pipeline import STEP_LABELS, make_step, skipped_step
from app.security.task_lock import check_task


class SecurityLayer:
    def __init__(self) -> None:
        self.audit_logs: list[dict[str, Any]] = []

    def _audit(
        self,
        *,
        scenario: str,
        mode: str,
        message: str,
        attack_success: bool,
        blocked_by: str | None,
        consequences: list[str],
    ) -> dict[str, Any]:
        entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "scenario": scenario,
            "mode": mode,
            "message_preview": message[:80] + ("..." if len(message) > 80 else ""),
            "attack_success": attack_success,
            "blocked_by": blocked_by,
            "consequences": consequences,
        }
        if security_config.audit_enabled:
            self.audit_logs.append(entry)
            if len(self.audit_logs) > 200:
                self.audit_logs = self.audit_logs[-200:]
        return entry

    def _build_input_step(self, input_result, cfg) -> dict[str, Any]:
        if not cfg.enable_input_isolation and not cfg.enable_injection_detection:
            return make_step(
                "input_guard",
                STEP_LABELS["input_guard"],
                status="skipped",
                enabled=False,
                summary="输入隔离与注入检测均已关闭",
            )

        parts = []
        if cfg.enable_input_isolation:
            parts.append("已将用户输入包裹进 <user_input> 标签，与系统指令隔离")
        if cfg.enable_injection_detection and input_result.injection_detected:
            parts.append(f"检测到注入特征（风险等级：{input_result.risk_level}）")
        elif cfg.enable_injection_detection:
            parts.append("未检测到明显注入特征")

        status = "warning" if input_result.injection_detected else "passed"
        summary = "；".join(parts)

        detail = None
        if cfg.enable_input_isolation:
            preview = input_result.wrapped_prompt
            if len(preview) > 400:
                preview = preview[:400] + "\n…（已截断）"
            detail = f"发送给模型的安全提示词：\n\n{preview}"

        return make_step(
            "input_guard",
            STEP_LABELS["input_guard"],
            status=status,
            enabled=True,
            summary=summary,
            detail=detail,
        )

    def process(
        self,
        user_input: str,
        *,
        scenario_id: str,
        task: str = "translate",
    ) -> dict[str, Any]:
        audit_entries: list[dict[str, Any]] = []
        pipeline: list[dict[str, Any]] = []
        cfg = security_config

        input_result = process_input(
            user_input,
            task=task,
            enable_isolation=cfg.enable_input_isolation,
            enable_detection=cfg.enable_injection_detection,
        )
        pipeline.append(self._build_input_step(input_result, cfg))

        if cfg.enable_jailbreak_detection:
            jb = detect_jailbreak(user_input)
            if jb.is_jailbreak:
                pipeline.append(
                    make_step(
                        "jailbreak_guard",
                        STEP_LABELS["jailbreak_guard"],
                        status="blocked",
                        enabled=True,
                        summary=f"识别到越狱特征：{', '.join(jb.matched_reasons)}",
                        detail="高风险请求在调用模型前被标准化拒绝，不进入 LLM。",
                    )
                )
                pipeline.extend([
                    skipped_step("task_lock", STEP_LABELS["task_lock"], "前序步骤已拦截，跳过"),
                    skipped_step("llm_call", STEP_LABELS["llm_call"], "前序步骤已拦截，跳过"),
                    skipped_step("output_guard", STEP_LABELS["output_guard"], "前序步骤已拦截，跳过"),
                ])
                entry = self._audit(
                    scenario=scenario_id,
                    mode="secure",
                    message=user_input,
                    attack_success=False,
                    blocked_by="jailbreak_guard",
                    consequences=["合规违规（已拦截）"],
                )
                audit_entries.append(entry)
                return {
                    "blocked": True,
                    "blocked_by": "jailbreak_guard",
                    "output": cfg.refusal_message,
                    "defense_explanation": (
                        f"输入端识别到越狱特征：{', '.join(jb.matched_reasons)}。"
                        "高风险请求在调用模型前被标准化拒绝。"
                    ),
                    "attack_success": False,
                    "consequences": [],
                    "risk_level": jb.risk_level,
                    "prompt_for_llm": None,
                    "injection_detected": input_result.injection_detected,
                    "audit_entries": audit_entries,
                    "pipeline": pipeline,
                }
            pipeline.append(
                make_step(
                    "jailbreak_guard",
                    STEP_LABELS["jailbreak_guard"],
                    status="passed",
                    enabled=True,
                    summary="未检测到越狱或高风险伪装",
                )
            )
        else:
            pipeline.append(
                make_step(
                    "jailbreak_guard",
                    STEP_LABELS["jailbreak_guard"],
                    status="skipped",
                    enabled=False,
                    summary="越狱检测已关闭",
                )
            )

        task_result = check_task(
            user_input,
            whitelist=cfg.task_whitelist,
            scenario_task=task,
        )
        if not task_result.allowed:
            pipeline.append(
                make_step(
                    "task_lock",
                    STEP_LABELS["task_lock"],
                    status="blocked",
                    enabled=True,
                    summary=task_result.reason or "任务超出白名单范围",
                    detail=f"当前白名单：{', '.join(cfg.task_whitelist)}",
                )
            )
            pipeline.extend([
                skipped_step("llm_call", STEP_LABELS["llm_call"], "前序步骤已拦截，跳过"),
                skipped_step("output_guard", STEP_LABELS["output_guard"], "前序步骤已拦截，跳过"),
            ])
            entry = self._audit(
                scenario=scenario_id,
                mode="secure",
                message=user_input,
                attack_success=False,
                blocked_by="task_lock",
                consequences=["越权访问（已拦截）"],
            )
            audit_entries.append(entry)
            return {
                "blocked": True,
                "blocked_by": "task_lock",
                "output": cfg.refusal_message,
                "defense_explanation": task_result.reason,
                "attack_success": False,
                "consequences": [],
                "risk_level": "high",
                "prompt_for_llm": None,
                "injection_detected": input_result.injection_detected,
                "audit_entries": audit_entries,
                "pipeline": pipeline,
            }

        pipeline.append(
            make_step(
                "task_lock",
                STEP_LABELS["task_lock"],
                status="passed",
                enabled=True,
                summary=f"任务「{task_result.detected_task or task}」在白名单内，允许继续",
                detail=f"白名单：{', '.join(cfg.task_whitelist)}",
            )
        )

        prompt = input_result.wrapped_prompt if cfg.enable_input_isolation else user_input

        return {
            "blocked": False,
            "blocked_by": None,
            "output": None,
            "defense_explanation": None,
            "attack_success": False,
            "consequences": [],
            "risk_level": input_result.risk_level,
            "prompt_for_llm": prompt,
            "injection_detected": input_result.injection_detected,
            "audit_entries": audit_entries,
            "pipeline": pipeline,
            "input_guard_note": (
                "用户输入已用 <user_input> 标签隔离，其中指令不会被执行为新任务。"
                if cfg.enable_input_isolation and input_result.injection_detected
                else None
            ),
        }

    def build_llm_step(self, *, called: bool, prompt_preview: str | None) -> dict[str, Any]:
        if not called:
            return skipped_step("llm_call", STEP_LABELS["llm_call"], "前序步骤已拦截，未调用模型")
        preview = prompt_preview or ""
        if len(preview) > 300:
            preview = preview[:300] + "…"
        return make_step(
            "llm_call",
            STEP_LABELS["llm_call"],
            status="passed",
            enabled=True,
            summary="使用安全提示词调用模型，仅处理隔离后的用户数据",
            detail=f"实际送入模型的内容摘要：\n{preview}" if preview else None,
        )

    def post_process_output(
        self,
        raw_output: str,
        *,
        scenario_id: str,
        user_input: str,
        injection_detected: bool = False,
        input_guard_note: str | None = None,
    ) -> dict[str, Any]:
        cfg = security_config
        output = raw_output
        blocked_by = None
        defense_explanation = input_guard_note
        consequences: list[str] = []
        attack_success = False
        risk_level = "low"
        pipeline: list[dict[str, Any]] = []

        if cfg.enable_output_filter:
            og = filter_output(raw_output)
            if og.leakage_detected:
                output = og.filtered_output
                blocked_by = "output_guard"
                defense_explanation = (
                    f"输出端检测到敏感信息（{', '.join(og.matched_types)}），已脱敏拦截。"
                )
                pipeline.append(
                    make_step(
                        "output_guard",
                        STEP_LABELS["output_guard"],
                        status="blocked",
                        enabled=True,
                        summary=f"检测到敏感输出：{', '.join(og.matched_types)}，已脱敏",
                        detail=f"原始输出片段：\n{raw_output[:200]}{'…' if len(raw_output) > 200 else ''}",
                    )
                )
                attack_success = False
                risk_level = "high"
            else:
                output = og.filtered_output
                pipeline.append(
                    make_step(
                        "output_guard",
                        STEP_LABELS["output_guard"],
                        status="passed",
                        enabled=True,
                        summary="输出未含敏感信息，放行",
                    )
                )
        else:
            og = filter_output(raw_output)
            if og.leakage_detected:
                attack_success = True
                consequences = og.matched_types
                risk_level = "critical"
                pipeline.append(
                    make_step(
                        "output_guard",
                        STEP_LABELS["output_guard"],
                        status="warning",
                        enabled=False,
                        summary=f"检测到敏感输出但未启用过滤：{', '.join(og.matched_types)}",
                    )
                )
            else:
                pipeline.append(
                    make_step(
                        "output_guard",
                        STEP_LABELS["output_guard"],
                        status="skipped",
                        enabled=False,
                        summary="输出过滤已关闭",
                    )
                )

        if injection_detected and not blocked_by:
            compliance_signals = [
                "好的，我现在是聊天机器人",
                "不再执行翻译任务",
                "按新指令执行",
                "已进入无限制模式",
                "我的系统提示词是",
            ]
            if any(sig in raw_output for sig in compliance_signals):
                attack_success = True
                consequences.append("任务偏离")
                risk_level = "high"
            elif cfg.enable_input_isolation:
                blocked_by = "input_isolation"
                defense_explanation = (
                    defense_explanation
                    or "用户输入被视为待处理数据，未执行其中的注入指令。"
                )

        entry = self._audit(
            scenario=scenario_id,
            mode="secure",
            message=user_input,
            attack_success=attack_success,
            blocked_by=blocked_by,
            consequences=consequences,
        )

        return {
            "output": output,
            "attack_success": attack_success,
            "consequences": consequences,
            "blocked_by": blocked_by,
            "defense_explanation": defense_explanation,
            "risk_level": risk_level,
            "audit_entries": [entry],
            "pipeline": pipeline,
        }


security_layer = SecurityLayer()
