from __future__ import annotations

import json
import os
from urllib import request

from .models import BlockCandidate, Finding


def _json_object_from_text(text: str) -> dict[str, object]:
    text = text.strip()
    if text.startswith("```"):
        lines = text.splitlines()
        if lines and lines[0].startswith("```"):
            lines = lines[1:]
        if lines and lines[-1].strip() == "```":
            lines = lines[:-1]
        text = "\n".join(lines).strip()
    try:
        payload = json.loads(text)
        return payload if isinstance(payload, dict) else {}
    except json.JSONDecodeError:
        pass
    start = text.find("{")
    end = text.rfind("}")
    if start == -1 or end == -1 or end <= start:
        return {}
    try:
        payload = json.loads(text[start:end + 1])
        return payload if isinstance(payload, dict) else {}
    except json.JSONDecodeError:
        return {}


def ollama_summary(
    findings: list[Finding],
    candidates: list[BlockCandidate],
    model: str | None = None,
    *,
    analysis: dict[str, object] | None = None,
    timeout: int | None = None,
) -> str:
    host = os.getenv("OLLAMA_HOST", "http://127.0.0.1:11434").rstrip("/")
    selected_model = model or os.getenv("OLLAMA_MODEL", "llama3.1")
    selected_timeout = timeout or int(os.getenv("OLLAMA_TIMEOUT", "180"))
    prompt = {
        "analysis": analysis or {},
        "findings": [finding.__dict__ for finding in findings],
        "block_candidates": [
            {"src_ip": candidate.src_ip, "score": candidate.score, "reasons": candidate.reasons}
            for candidate in candidates[:20]
        ],
    }
    body = json.dumps(
        {
            "model": selected_model,
            "stream": False,
            "options": {
                "num_predict": 350,
                "temperature": 0.2,
            },
            "prompt": (
                "You are SignalScope, a local security operations analyst. Analyze only the supplied telemetry. "
                "Never describe the input as JSON, a SIEM object, a dataset, or an array. Never ask the user what to focus on. "
                "Return exactly these short sections: Assessment, Priority entities, Evidence, Recommended next action. "
                "Use actual entity names, stream names, counts, scores, and field deviations from the supplied data. "
                "If evidence is insufficient, say that explicitly and name the missing field or stream. "
                "Do not recommend blocking private/internal client IPs unless the data explicitly proves compromise. "
                f"\n\nTelemetry:\n{json.dumps(prompt)}"
            ),
        }
    ).encode("utf-8")
    req = request.Request(f"{host}/api/generate", data=body, method="POST", headers={"Content-Type": "application/json"})
    with request.urlopen(req, timeout=selected_timeout) as response:
        data = json.loads(response.read().decode("utf-8"))
    return str(data.get("response", "")).strip()


def ollama_dashboard_assessment(analysis: dict[str, object], model: str | None = None, timeout: int | None = None) -> str:
    compact = {
        "summary": analysis.get("summary", {}),
        "anomaly_summary": analysis.get("anomaly_summary", {}),
        "top_anomalies": analysis.get("anomalies", [])[:5],
        "top_recommendations": analysis.get("recommendations", [])[:5],
        "block_candidates": analysis.get("block_candidates", [])[:5],
        "policy_findings": analysis.get("policy_findings", [])[:5],
        "event_context": analysis.get("event_context", {}),
        "diagnostics": analysis.get("diagnostics", {}),
        "capabilities": analysis.get("capabilities", {}),
        "cross_source_correlations": analysis.get("cross_source_correlations", [])[:20],
        "incidents": analysis.get("incidents", [])[:10],
        "profile_suggestions": analysis.get("profile_suggestions", [])[:10],
        "field_deviations": analysis.get("field_deviations", {}),
        "feedback": analysis.get("feedback", []),
    }
    return ollama_summary(
        [],
        [],
        model,
        analysis={
            "task": (
                "Write a concise dashboard analyst note. Compare activity across every listed entity, "
                "identify the most unusual entity or behavior, and state the next investigation step. "
                "Mention policyid=0 as implicit deny/drop, not an editable policy."
            ),
            "data": compact,
        },
        timeout=timeout,
    )


def _compact_profile_suggestion(item: dict[str, object]) -> dict[str, object]:
    discovery = item.get("discovery", {}) if isinstance(item.get("discovery"), dict) else {}
    selected = discovery.get("selected_fields", {}) if isinstance(discovery.get("selected_fields"), dict) else {}
    return {
        "stream_id": item.get("stream_id"),
        "stream_name": item.get("stream_name"),
        "events": item.get("events"),
        "detected_log_type": item.get("detected_log_type", ""),
        "common_fields": item.get("common_fields", [])[:25],
        "shared_fields": item.get("shared_fields", [])[:20],
        "top_fields": discovery.get("top_fields", [])[:25],
        "selected_fields": selected,
        "rejected_fields": discovery.get("rejected_fields", [])[:10],
        "heuristic_profile": item.get("profile", {}),
    }


def ollama_profile_advice(suggestions: list[dict[str, object]], model: str | None = None, timeout: int | None = None) -> list[dict[str, object]]:
    host = os.getenv("OLLAMA_HOST", "http://127.0.0.1:11434").rstrip("/")
    selected_model = model or os.getenv("FGAI_PROFILE_ADVISOR_MODEL", "qwen3:8b")
    selected_timeout = timeout or int(os.getenv("FGAI_PROFILE_ADVISOR_TIMEOUT", "120"))
    compact = [_compact_profile_suggestion(item) for item in suggestions[:10]]
    body = json.dumps(
        {
            "model": selected_model,
            "stream": False,
            "format": "json",
            "options": {"num_predict": 1200, "temperature": 0.1},
            "prompt": (
                "You are SignalScope's local profile advisor. Infer stream profile mappings from observed field statistics. "
                "Return only valid JSON with this schema: "
                "{\"profiles\":[{\"stream_id\":\"...\",\"entity_fields\":[\"...\"],\"timestamp_field\":\"...\","
                "\"categorical_fields\":[\"...\"],\"numeric_fields\":[\"...\"],\"detectors\":{\"auth_failure\":{\"enabled\":true,\"minimum\":5,\"z_threshold\":3}},"
                "\"relationship_fields\":[{\"left\":\"username\",\"right\":\"srcip\",\"name\":\"user source IP\"}],"
                "\"field_roles\":{\"field\":\"entity|identity|asset|session|status|action|resource|metric|context\"},"
                "\"correlation_roles\":{\"entity\":[\"...\"],\"identity\":[\"...\"],\"asset\":[\"...\"],\"session\":[\"...\"],\"activity\":[\"...\"],\"resource\":[\"...\"],\"status\":[\"...\"]},"
                "\"reason\":\"short reason\"}]}. "
                "Use only field names present in common_fields, shared_fields aliases, top_fields, selected_fields, or heuristic_profile. "
                "Do not invent fields. Do not include raw message/full_message/raw/answer fields. "
                "Treat shared_fields as deterministic evidence that a field or semantic group appears across streams. "
                "For custom namespaces such as lcs_* infer roles from the real field names and observed stats, for example tenant/customer/request/session/correlation IDs. "
                "Prefer profile fields that connect streams: source or client IPs, usernames/accounts, hostnames, event IDs, session/correlation/request IDs, status/result/action/type, service/app, URL/domain/resource, and stable numeric metrics. "
                "Use categorical_fields for low-cardinality behavior dimensions, numeric_fields for counters/durations/sizes, and entity_fields for fields that identify a user, host, IP, account, device, or application actor. "
                "Use relationship_fields for behavior pairs where a left identity should learn normal right-side values, such as username->srcip, username->hostname, host->process.name, srcip->dstip, or account->application. "
                "Allowed detectors are auth_failure, dns_query, deny_action. Prefer canonical fields such as username, hostname, eventid, srcip, dstip when present, but keep useful stream-specific fields too. "
                f"\n\nObserved streams:\n{json.dumps(compact, sort_keys=True)}"
            ),
        }
    ).encode("utf-8")
    req = request.Request(f"{host}/api/generate", data=body, method="POST", headers={"Content-Type": "application/json"})
    with request.urlopen(req, timeout=selected_timeout) as response:
        data = json.loads(response.read().decode("utf-8"))
    payload = _json_object_from_text(str(data.get("response", "{}")))
    profiles = payload.get("profiles", [])
    return profiles if isinstance(profiles, list) else []
