"""
Constitution Enforcer — Automated enforcement of all 45 Brynq laws.

Every broker operation passes through the enforcer. Violations are
logged, penalized, and escalated automatically.

Enforcement levels:
  WARNING    → Mediator advice injected (first offense)
  COOLDOWN   → Rate limit halved for 5 minutes
  ESCALATION → Posted to #_escalations, human notified
  DISCONNECT → Session flagged, all operations blocked

The enforcer is the law. The human is above the law.
"""

import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from .server import (
    BROKER_DIR,
    CHANNELS_DIR,
    SESSION_ID,
    SESSION_NAME,
    PLATFORM,
    _append_jsonl_sync,
)

VIOLATIONS_DIR = BROKER_DIR / "violations"
VIOLATIONS_DIR.mkdir(parents=True, exist_ok=True)

# ── Violation Tracking ─────────────────────────────────────────────────────

_ENFORCEMENT_LEVELS = {
    "warning": {"max_violations": 2, "action": "mediator advice"},
    "cooldown": {"max_violations": 5, "action": "rate limit halved for 5 min"},
    "escalation": {"max_violations": 8, "action": "human notified"},
    "disconnect": {"max_violations": 12, "action": "session blocked"},
}

# In-memory cooldown state
_cooldown_until: float = 0.0
_blocked: bool = False


def _get_violation_file(session_id: str) -> Path:
    return VIOLATIONS_DIR / f"{session_id}.json"


def _load_violations(session_id: str) -> dict:
    path = _get_violation_file(session_id)
    try:
        return json.loads(path.read_text("utf-8"))
    except (FileNotFoundError, json.JSONDecodeError):
        return {
            "session_id": session_id,
            "total_violations": 0,
            "violations": [],
            "enforcement_level": "none",
            "cooldown_until": None,
            "blocked": False,
        }


def _save_violations(session_id: str, data: dict) -> None:
    path = _get_violation_file(session_id)
    path.write_text(json.dumps(data, indent=2, default=str))


def record_violation(law: int, description: str, severity: str = "warning",
                     session_id: str = None, session_name: str = None) -> dict:
    """Record a constitutional violation and determine enforcement action.

    Returns: {"level": str, "action": str, "message": str}
    """
    global _cooldown_until, _blocked

    sid = session_id or SESSION_ID
    sname = session_name or SESSION_NAME

    data = _load_violations(sid)
    data["total_violations"] += 1

    violation = {
        "law": law,
        "description": description,
        "severity": severity,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_name": sname,
    }
    data["violations"].append(violation)

    # Keep only last 50 violations
    if len(data["violations"]) > 50:
        data["violations"] = data["violations"][-50:]

    # Determine enforcement level
    total = data["total_violations"]
    if total >= 12:
        level = "disconnect"
        _blocked = True
        data["blocked"] = True
    elif total >= 8:
        level = "escalation"
    elif total >= 5:
        level = "cooldown"
        _cooldown_until = time.monotonic() + 300  # 5 minutes
        data["cooldown_until"] = datetime.now(timezone.utc).isoformat()
    else:
        level = "warning"

    data["enforcement_level"] = level
    _save_violations(sid, data)

    action = _ENFORCEMENT_LEVELS.get(level, {}).get("action", "unknown")
    message = f"[LAW {law} VIOLATION] {description} — Enforcement: {level} ({action})"

    # Log to #_violations channel
    _append_jsonl_sync(CHANNELS_DIR / "_violations.jsonl", {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": sid,
        "session_name": sname,
        "platform": PLATFORM,
        "channel": "_violations",
        "msg_type": "violation",
        "message": message,
        "law": law,
        "severity": severity,
        "enforcement_level": level,
    })

    # Auto-escalate to #_escalations for serious violations
    if level in ("escalation", "disconnect"):
        _append_jsonl_sync(CHANNELS_DIR / "_escalations.jsonl", {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "session_id": "enforcer",
            "session_name": "Brynq-Enforcer",
            "platform": "system",
            "channel": "_escalations",
            "msg_type": "escalation",
            "message": f"HUMAN REVIEW NEEDED: {sname} has {total} violations. Latest: Law {law} — {description}. Level: {level}.",
        })

    return {"level": level, "action": action, "message": message}


def check_session_status(session_id: str = None) -> dict:
    """Check if a session is in good standing, cooldown, or blocked.

    Returns: {"status": str, "can_post": bool, "violations": int, "level": str}
    """
    global _cooldown_until, _blocked

    sid = session_id or SESSION_ID
    data = _load_violations(sid)

    if data.get("blocked"):
        _blocked = True
        return {
            "status": "blocked",
            "can_post": False,
            "violations": data["total_violations"],
            "level": "disconnect",
            "reason": "Too many violations. Contact human operator.",
        }

    if _cooldown_until > time.monotonic():
        remaining = int(_cooldown_until - time.monotonic())
        return {
            "status": "cooldown",
            "can_post": True,  # Can post but at reduced rate
            "violations": data["total_violations"],
            "level": "cooldown",
            "reason": f"Cooldown: reduced rate limit for {remaining}s",
            "rate_limit_multiplier": 0.5,
        }

    return {
        "status": "good_standing",
        "can_post": True,
        "violations": data["total_violations"],
        "level": data.get("enforcement_level", "none"),
    }


def pardon(session_id: str) -> dict:
    """Human operator pardons a session — resets all violations.

    Returns the cleared record.
    """
    global _cooldown_until, _blocked

    data = _load_violations(session_id)
    old_total = data["total_violations"]

    data["total_violations"] = 0
    data["violations"] = []
    data["enforcement_level"] = "none"
    data["blocked"] = False
    data["cooldown_until"] = None
    _save_violations(session_id, data)

    if session_id == SESSION_ID:
        _cooldown_until = 0.0
        _blocked = False

    _append_jsonl_sync(CHANNELS_DIR / "_violations.jsonl", {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": "enforcer",
        "session_name": "Brynq-Enforcer",
        "platform": "system",
        "channel": "_violations",
        "msg_type": "pardon",
        "message": f"PARDONED: {session_id} cleared of {old_total} violations by human operator.",
    })

    return {"session_id": session_id, "pardoned": old_total, "status": "good_standing"}


# ── Pre-Operation Checks ──────────────────────────────────────────────────


def enforce_pre_post(message: str, channel: str) -> tuple[bool, str, Optional[str]]:
    """Run all constitution checks before allowing a message post.

    Returns (allowed, reason, mediation_message).
    """
    # Law 26: Check if session is blocked (disconnect level)
    status = check_session_status()
    if not status["can_post"]:
        return False, f"BLOCKED: {status['reason']}", None

    # Law 10: Message length
    if len(message) > 5000:
        record_violation(10, f"Message too long: {len(message)} chars (max 5000)")
        return False, "Law 10: Message exceeds 5,000 character limit. Use a file_ref payload.", None

    # Law 5: Empty/whitespace messages
    if not message or not message.strip():
        return False, "Law 5: Empty message", None

    # Law 40: Reserved channel protection
    if channel.startswith("_") and channel not in ("_system",):
        record_violation(40, f"Attempted to post to reserved channel #{channel}")
        return False, f"Law 40: #{channel} is a system channel. Agents cannot post directly.", None

    # Law 6: Rate limit (with cooldown multiplier)
    from .guardrails import check_rate_limit
    allowed, rate_reason = check_rate_limit()
    if not allowed:
        record_violation(6, f"Rate limit exceeded on #{channel}")
        return False, f"Law 6: {rate_reason}", None

    # Law 8: Conflict detection
    from .guardrails import detect_conflict
    conflict = detect_conflict(message)
    mediation = None

    if conflict["severity"] == "escalate":
        record_violation(8, f"Heated argument detected: {conflict['triggers']}", severity="escalation")
        mediation = (
            f"[ENFORCER] Law 8 violation: Argumentative message detected. "
            f"Triggers: {', '.join(conflict['triggers'])}. "
            f"Use broker_propose for disagreements."
        )
    elif conflict["severity"] == "heated":
        record_violation(8, f"Conflict detected: {conflict['triggers']}", severity="warning")
        mediation = (
            f"[ENFORCER] Law 8 warning: Confrontational language detected. "
            f"Please use broker_propose instead of arguing."
        )

    # Law 28: Credential detection
    credential_patterns = [
        "sk-", "sk_live_", "sk_test_", "xoxb-", "xoxp-",
        "ghp_", "gho_", "github_pat_",
        "AKIA", "aws_secret",
        "eyJ",  # JWT tokens
        "hooks.slack.com", "discord.com/api/webhooks",
    ]
    msg_lower = message.lower()
    for pattern in credential_patterns:
        if pattern.lower() in msg_lower:
            record_violation(28, f"Potential credential detected (pattern: {pattern})", severity="escalation")
            return False, f"Law 28: Message appears to contain credentials (matched: {pattern}). Use environment variables instead.", None

    return True, "ok", mediation


def enforce_pre_task_accept(task_id: str, current_tasks: list) -> tuple[bool, str]:
    """Check Law 20 (one task at a time) before accepting."""
    active = [t for t in current_tasks if t.get("status") == "accepted"
              and t.get("assignee_session") == SESSION_ID]
    if len(active) >= 2:
        record_violation(20, f"Tried to accept task {task_id} with {len(active)} active tasks")
        return False, f"Law 20: You have {len(active)} active tasks. Complete or fail them before accepting more."
    return True, "ok"


def enforce_pre_edit(filepath: str) -> tuple[bool, str]:
    """Check Law 11 (claim before edit) before file operations."""
    from .guardrails import check_file_lock
    lock = check_file_lock(filepath)
    if lock and lock.get("session_id") != SESSION_ID:
        holder = lock.get("session_name", "unknown")
        record_violation(11, f"Attempted to edit locked file {filepath} (held by {holder})")
        return False, f"Law 11: {filepath} is locked by {holder}. Call broker_claim_file first or wait for release."
    return True, "ok"


def enforce_profile_skills(claimed_skills: list, completed_tasks: list) -> list:
    """Law 33: Flag unverified skills based on task history."""
    verified = set()
    for task in completed_tasks:
        # Extract skills from completed task descriptions
        desc = (task.get("description", "") + " " + task.get("title", "")).lower()
        for skill in claimed_skills:
            if skill.lower() in desc:
                verified.add(skill)

    unverified = [s for s in claimed_skills if s not in verified]
    return unverified


def enforce_no_discrimination(job: dict, applicant: dict, reason: str) -> tuple[bool, str]:
    """Law 37: Check for platform discrimination in hiring."""
    if "platform" in reason.lower() or applicant.get("platform", "") in reason.lower():
        record_violation(37, f"Possible platform discrimination in hiring: {reason}")
        return False, "Law 37: Hiring decisions must be based on skills and reputation, not platform."
    return True, "ok"


def get_violation_summary(session_id: str = None) -> dict:
    """Get a summary of violations for a session."""
    sid = session_id or SESSION_ID
    data = _load_violations(sid)
    recent = data.get("violations", [])[-5:]
    return {
        "session_id": sid,
        "total_violations": data.get("total_violations", 0),
        "enforcement_level": data.get("enforcement_level", "none"),
        "blocked": data.get("blocked", False),
        "recent_violations": recent,
        "status": check_session_status(sid)["status"],
    }
