from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from .anomaly import anomaly_summary, detect_source_anomalies
from .dashboard import serve_dashboard
from .llm import ollama_summary
from .logs import local_in_failures, read_events, summarize_events, top_field_values
from .mitigation import FortiGateClient, parse_allowlist, suggest_block_candidates
from .policies import audit_policies, read_policies
from .recommendations import build_recommendations
from .syslog_server import listen_udp_syslog
from .monitor import monitor_loop
from .threat_intel import enrich_ips, is_public_ip


def _print_json(data: object) -> None:
    print(json.dumps(data, indent=2, sort_keys=True))


def analyze_logs(args: argparse.Namespace) -> int:
    events = read_events(args.logs)
    candidates = suggest_block_candidates(
        events,
        min_events=args.min_events,
        min_score=args.min_score,
        allowlist=parse_allowlist(args.allowlist),
    )
    anomalies = detect_source_anomalies(events, limit=args.anomaly_limit)
    analysis = {
        "summary": summarize_events(events),
        "anomaly_summary": anomaly_summary(anomalies),
        "diagnostics": {
            "top_source_ips": top_field_values(events, "srcip", limit=10),
            "top_services": top_field_values(events, "service", limit=10),
            "top_actions": top_field_values(events, "action", limit=10),
            "top_subtypes": top_field_values(events, "subtype", limit=10),
            "local_in_failures": local_in_failures(events, limit=10),
        },
        "block_candidates": [
            {"src_ip": candidate.src_ip, "score": candidate.score, "reasons": candidate.reasons}
            for candidate in candidates
        ],
        "anomalies": [
            {
                "subject": finding.subject,
                "score": finding.score,
                "severity": finding.severity,
                "confidence": finding.confidence,
                "reasons": finding.reasons,
                "evidence": finding.evidence,
            }
            for finding in anomalies
        ],
    }
    _print_json(analysis)
    if args.llm:
        print("\nLLM summary:")
        print(ollama_summary([], candidates, args.model, analysis=analysis, timeout=args.llm_timeout))
    return 0


def audit_policy_file(args: argparse.Namespace) -> int:
    findings = audit_policies(read_policies(args.config))
    _print_json([finding.__dict__ for finding in findings])
    if args.llm:
        print("\nLLM summary:")
        print(ollama_summary(findings, [], args.model, timeout=args.llm_timeout))
    return 0


def suggest_blocks(args: argparse.Namespace) -> int:
    events = read_events(args.logs)
    candidates = suggest_block_candidates(
        events,
        min_events=args.min_events,
        min_score=args.min_score,
        allowlist=parse_allowlist(args.allowlist),
    )
    if not candidates:
        print("No block candidates met the current thresholds.")
        return 0

    for candidate in candidates:
        print(f"{candidate.src_ip} score={candidate.score} reasons={', '.join(candidate.reasons)}")
        if not args.execute:
            print("  dry-run: not blocked")
            continue
        client = FortiGateClient.from_env()
        reason = f"fgAI UTM mitigation score={candidate.score}"
        response = client.quarantine_ip(candidate.src_ip, args.expiry_minutes, reason)
        print(f"  blocked: {response}")
    return 0


def detect_anomalies(args: argparse.Namespace) -> int:
    events = read_events(args.logs)
    anomalies = detect_source_anomalies(events, limit=args.limit)
    output = {
        "summary": anomaly_summary(anomalies),
        "anomalies": [
            {
                "subject": finding.subject,
                "score": finding.score,
                "severity": finding.severity,
                "confidence": finding.confidence,
                "reasons": finding.reasons,
                "evidence": finding.evidence,
            }
            for finding in anomalies
            if finding.score >= args.min_score
        ],
    }
    _print_json(output)
    if args.llm:
        print("\nLLM summary:")
        print(ollama_summary([], [], args.model, analysis=output, timeout=args.llm_timeout))
    return 0


def recommend(args: argparse.Namespace) -> int:
    events = read_events(args.logs)
    anomalies = detect_source_anomalies(events, limit=args.limit)
    intel_ips = sorted(
        {
            ip
            for event in events
            for ip in (event.src_ip, event.dst_ip)
            if is_public_ip(ip)
        }
    )
    reputation = enrich_ips(intel_ips, limit=args.intel_limit) if args.threat_intel else {}
    recommendations = build_recommendations(events, anomalies, reputation)
    output = {
        "recommendations": [
            {
                "subject": item.subject,
                "score": item.score,
                "severity": item.severity,
                "title": item.title,
                "recommendation": item.recommendation,
                "reasons": item.reasons,
                "related_policy_ids": item.related_policy_ids,
                "related_services": item.related_services,
            }
            for item in recommendations
            if item.score >= args.min_score
        ],
        "reputation": reputation,
    }
    _print_json(output)
    if args.llm:
        print("\nLLM summary:")
        print(ollama_summary([], [], args.model, analysis=output, timeout=args.llm_timeout))
    return 0


def test_connection(args: argparse.Namespace) -> int:
    client = FortiGateClient.from_env()
    status = client.system_status()
    results = status.get("results", status)
    print("FortiGate API connection OK")
    _print_json(results)
    return 0


def fetch_policies(args: argparse.Namespace) -> int:
    client = FortiGateClient.from_env()
    policies = client.firewall_policies()
    if args.output:
        output = Path(args.output)
        output.parent.mkdir(parents=True, exist_ok=True)
        output.write_text(json.dumps(policies, indent=2, sort_keys=True), encoding="utf-8")
        print(f"Wrote {output}")
    else:
        _print_json(policies)
    return 0


def listen_syslog(args: argparse.Namespace) -> int:
    listen_udp_syslog(
        args.host,
        args.port,
        args.output,
        rotate_bytes=args.rotate_bytes,
        rotate_count=args.rotate_count,
        quiet=args.quiet,
    )
    return 0


def run_monitor(args: argparse.Namespace) -> int:
    monitor_loop(
        args.logs,
        args.output,
        policy_path=args.policies,
        interval=args.interval,
        anomaly_limit=args.anomaly_limit,
        llm=args.llm,
        llm_interval=args.llm_interval,
        llm_model=args.model,
        llm_timeout=args.llm_timeout,
        baseline_path=args.baseline_db,
        config_path=args.config_file,
        history_path=args.history_db,
    )
    return 0


def run_dashboard(args: argparse.Namespace) -> int:
    serve_dashboard(args.host, args.port, args.status_file, image_dir=args.image_dir, config_file=args.config_file)
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Local FortiGate AI/ML inspection tool")
    subparsers = parser.add_subparsers(required=True)

    logs = subparsers.add_parser("analyze-logs", help="Analyze local FortiGate logs")
    logs.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
    logs.add_argument("--min-events", type=int, default=3)
    logs.add_argument("--min-score", type=int, default=7)
    logs.add_argument("--allowlist", default=None, help="Comma-separated IPs/CIDRs never to block")
    logs.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
    logs.add_argument("--model", default=None, help="Ollama model name")
    logs.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
    logs.add_argument("--anomaly-limit", type=int, default=20, help="Maximum anomaly findings to include")
    logs.set_defaults(func=analyze_logs)

    anomalies = subparsers.add_parser("detect-anomalies", help="Score likely traffic anomalies by source IP")
    anomalies.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
    anomalies.add_argument("--limit", type=int, default=20, help="Maximum anomaly findings to include")
    anomalies.add_argument("--min-score", type=int, default=1, help="Minimum anomaly score to output")
    anomalies.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
    anomalies.add_argument("--model", default=None, help="Ollama model name")
    anomalies.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
    anomalies.set_defaults(func=detect_anomalies)

    recommendations = subparsers.add_parser("recommend", help="Generate policy and response recommendations from anomalies")
    recommendations.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
    recommendations.add_argument("--limit", type=int, default=20, help="Maximum anomaly findings to evaluate")
    recommendations.add_argument("--min-score", type=int, default=35, help="Minimum recommendation score to output")
    recommendations.add_argument("--threat-intel", action="store_true", help="Use enabled external threat intelligence lookups")
    recommendations.add_argument("--intel-limit", type=int, default=25, help="Maximum public IPs to enrich")
    recommendations.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
    recommendations.add_argument("--model", default=None, help="Ollama model name")
    recommendations.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
    recommendations.set_defaults(func=recommend)

    policies = subparsers.add_parser("audit-policies", help="Audit FortiOS firewall policy config")
    policies.add_argument("--config", required=True, help="Path to FortiOS config backup")
    policies.add_argument("--llm", action="store_true", help="Ask local Ollama to summarize results")
    policies.add_argument("--model", default=None, help="Ollama model name")
    policies.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
    policies.set_defaults(func=audit_policy_file)

    blocks = subparsers.add_parser("suggest-blocks", help="Suggest or execute guarded source IP blocks")
    blocks.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
    blocks.add_argument("--min-events", type=int, default=3)
    blocks.add_argument("--min-score", type=int, default=7)
    blocks.add_argument("--allowlist", default=None, help="Comma-separated IPs/CIDRs never to block")
    blocks.add_argument("--execute", action="store_true", help="Actually call the FortiGate quarantine API")
    blocks.add_argument("--expiry-minutes", type=int, default=60)
    blocks.set_defaults(func=suggest_blocks)

    connection = subparsers.add_parser("test-connection", help="Test FortiGate REST API credentials")
    connection.set_defaults(func=test_connection)

    fetch = subparsers.add_parser("fetch-policies", help="Fetch firewall policies through the FortiGate REST API")
    fetch.add_argument("--output", help="Write JSON response to this file")
    fetch.set_defaults(func=fetch_policies)

    listener = subparsers.add_parser("listen-syslog", help="Listen for UDP syslog and append to a local log file")
    listener.add_argument("--host", default="0.0.0.0", help="Bind address")
    listener.add_argument("--port", type=int, default=5514, help="UDP port. Use 514 only with sudo/capability.")
    listener.add_argument("--output", default="logs/fg_syslog.jsonl", help="File to append received logs to")
    listener.add_argument("--rotate-bytes", type=int, default=25 * 1024 * 1024, help="Rotate active log at this size; 0 disables rotation")
    listener.add_argument("--rotate-count", type=int, default=14, help="Number of compressed log archives to retain")
    listener.add_argument("--quiet", action="store_true", help="Do not print each received syslog message")
    listener.set_defaults(func=listen_syslog)

    monitor = subparsers.add_parser("monitor", help="Continuously analyze logs and write dashboard status JSON")
    monitor.add_argument("--logs", required=True, help="Path to syslog JSONL or key/value log file")
    monitor.add_argument("--output", default="state/fgai-status.json", help="Status JSON written for dashboard")
    monitor.add_argument("--policies", default=None, help="Optional FortiGate policy JSON/config file to audit continuously")
    monitor.add_argument("--interval", type=int, default=10, help="Seconds between analysis runs")
    monitor.add_argument("--anomaly-limit", type=int, default=20, help="Maximum anomaly findings to include")
    monitor.add_argument("--baseline-db", default="state/fgai-baseline.sqlite3", help="SQLite database for historical behavior baselines")
    monitor.add_argument("--config-file", default="state/fgai-config.json", help="Runtime configuration written by dashboard")
    monitor.add_argument("--history-db", default="state/signalscope-history.sqlite3", help="Aggregate history database for dashboard trends")
    monitor.add_argument("--llm", action="store_true", help="Generate cached Ollama analyst note for dashboard")
    monitor.add_argument("--llm-interval", type=int, default=300, help="Seconds between Ollama dashboard assessments")
    monitor.add_argument("--model", default=None, help="Ollama model name")
    monitor.add_argument("--llm-timeout", type=int, default=None, help="Ollama request timeout in seconds")
    monitor.set_defaults(func=run_monitor)

    dashboard = subparsers.add_parser("dashboard", help="Serve local fgAI dashboard")
    dashboard.add_argument("--host", default="127.0.0.1", help="Dashboard bind address")
    dashboard.add_argument("--port", type=int, default=8088, help="Dashboard TCP port")
    dashboard.add_argument("--status-file", default="state/fgai-status.json", help="Status JSON produced by monitor")
    dashboard.add_argument("--image-dir", default="images", help="Directory containing dashboard images")
    dashboard.add_argument("--config-file", default="state/fgai-config.json", help="Local runtime configuration JSON")
    dashboard.set_defaults(func=run_dashboard)

    return parser


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    try:
        return args.func(args)
    except Exception as exc:
        print(f"fgai: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
