import socket
import logging
from .base import Finding

logger = logging.getLogger(__name__)

CHECK_NAME = 'camera_rtsp'
TIMEOUT = 5
RTSP_PORT = 554


def _tcp_open(host: str, port: int) -> bool:
    try:
        with socket.create_connection((host, port), timeout=TIMEOUT):
            return True
    except (OSError, socket.timeout):
        return False


def _rtsp_request(host: str, method: str, seq: int, extra_headers: str = '') -> str:
    try:
        with socket.create_connection((host, RTSP_PORT), timeout=TIMEOUT) as s:
            request = (
                f'{method} rtsp://{host}/ RTSP/1.0\r\n'
                f'CSeq: {seq}\r\n'
                f'{extra_headers}'
                '\r\n'
            )
            s.sendall(request.encode())
            response = s.recv(4096).decode('utf-8', errors='replace')
        return response
    except Exception as e:
        return f'ERROR: {e}'


def _parse_rtsp_status(response: str) -> int:
    """Extract HTTP-style status code from RTSP response."""
    try:
        first_line = response.splitlines()[0]
        return int(first_line.split()[1])
    except (IndexError, ValueError):
        return 0


def run(profile) -> list:
    findings = []
    cameras = profile.cameras or []

    if not cameras:
        return [Finding(
            check_name=CHECK_NAME,
            severity='info',
            title='No camera IPs configured',
            detail='Add camera IPs to the scan profile to enable RTSP unauthenticated access check.',
            raw={},
        )]

    for ip in cameras:
        raw = {'camera_ip': ip}

        if not _tcp_open(ip, RTSP_PORT):
            findings.append(Finding(
                check_name=CHECK_NAME,
                severity='info',
                title=f'{ip}: RTSP port 554 closed',
                detail=f'Port 554 is not open on {ip}. Camera may be offline or not using RTSP.',
                raw=raw,
            ))
            continue

        options_resp = _rtsp_request(ip, 'OPTIONS', 1)
        raw['options_response'] = options_resp[:500]
        options_status = _parse_rtsp_status(options_resp)

        if options_status == 0:
            findings.append(Finding(
                check_name=CHECK_NAME,
                severity='info',
                title=f'{ip}: RTSP OPTIONS failed',
                detail=f'Got unexpected RTSP OPTIONS response from {ip}.',
                raw=raw,
            ))
            continue

        describe_resp = _rtsp_request(ip, 'DESCRIBE', 2, 'Accept: application/sdp\r\n')
        raw['describe_response'] = describe_resp[:500]
        describe_status = _parse_rtsp_status(describe_resp)

        if describe_status == 200:
            findings.append(Finding(
                check_name=CHECK_NAME,
                severity='critical',
                title=f'{ip}: RTSP stream accessible without credentials',
                detail=(
                    f'Camera at {ip} returned 200 to DESCRIBE without authentication. '
                    'Live stream may be publicly accessible on the LAN.'
                ),
                raw=raw,
            ))
        elif describe_status in (401, 403):
            findings.append(Finding(
                check_name=CHECK_NAME,
                severity='ok',
                title=f'{ip}: RTSP requires authentication',
                detail=f'Camera at {ip} returned {describe_status} to DESCRIBE — auth is enforced.',
                raw=raw,
            ))
        else:
            findings.append(Finding(
                check_name=CHECK_NAME,
                severity='ok',
                title=f'{ip}: RTSP DESCRIBE returned {describe_status}',
                detail=f'Camera at {ip} responded with status {describe_status} — no unauthenticated stream detected.',
                raw=raw,
            ))

    return findings
