"""
Optional ClamAV INSTREAM scan over TCP (clamd).

Set CLAMD_TCP_HOST (and optionally CLAMD_TCP_PORT, default 3310) in Django settings
or env to enable. If unset or connection fails, scanning is skipped (no error).
"""

from __future__ import annotations

import logging
import socket
import struct
from typing import BinaryIO

from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

logger = logging.getLogger(__name__)

INSTREAM = b"zINSTREAM\0"
CHUNK_MAX = 2048


def _read_clamd_response(sock: socket.socket) -> str:
    parts: list[bytes] = []
    while True:
        chunk = sock.recv(4096)
        if not chunk:
            break
        parts.append(chunk)
        if b"\0" in chunk or chunk.endswith(b"\n"):
            break
    return b"".join(parts).decode("utf-8", errors="replace").strip()


def scan_stream_optional(stream: BinaryIO) -> None:
    """
    If CLAMD_TCP_HOST is set, send stream to clamd INSTREAM; raise ValidationError if infected.
    """
    host = getattr(settings, "CLAMD_TCP_HOST", None) or ""
    if not str(host).strip():
        return
    port = int(getattr(settings, "CLAMD_TCP_PORT", 3310) or 3310)
    try:
        stream.seek(0)
    except (AttributeError, OSError):
        return
    try:
        with socket.create_connection((host, port), timeout=10) as sock:
            sock.sendall(INSTREAM)
            while True:
                data = stream.read(CHUNK_MAX)
                if not data:
                    sock.sendall(struct.pack("!I", 0))
                    break
                sock.sendall(struct.pack("!I", len(data)))
                sock.sendall(data)
            reply = _read_clamd_response(sock)
    except OSError as exc:
        logger.warning("clamd scan skipped (connection error): %s", exc)
        return
    finally:
        try:
            stream.seek(0)
        except (AttributeError, OSError):
            pass

    if "FOUND" in reply.upper():
        raise ValidationError(_("Uploaded file failed virus scan."))
    if reply and "OK" not in reply.upper() and "FOUND" not in reply.upper():
        logger.warning("clamd unexpected reply: %s", reply)
