"""Fail-closed, runtime-injected configuration. No private deployment defaults."""
from dataclasses import dataclass, field
import ipaddress
import json
import os
from pathlib import Path
import ssl
import hashlib
from urllib.parse import urlsplit

from control_sidecar.protocol import secret, loads


def origin(value, scheme):
    p = urlsplit(value)
    if (p.scheme != scheme or not p.hostname or p.username or p.password or p.query or p.fragment
            or p.path not in ('', '/') or any(c.isspace() for c in value)):
        raise ValueError('INVALID_ORIGIN')
    _ = p.port
    return value.rstrip('/')


def event_target(value, manager):
    target = value or manager + '/api/v1/events'
    p = urlsplit(target)
    if (p.scheme not in ('http', 'https') or p.hostname != urlsplit(manager).hostname
            or p.path != '/api/v1/events' or p.username or p.password or p.query or p.fragment
            or p.port == 0 or any(c.isspace() for c in target)):
        raise ValueError('EVENT_ENDPOINT_INVALID')
    return target


@dataclass(frozen=True, repr=False)
class Config:
    server_id: str
    manager: str
    runtime: str
    manager_ca: str
    runtime_token: str = field(repr=False)
    relay_token: str = field(repr=False)
    login_body: dict = field(repr=False)
    password: str = field(repr=False)
    request_seconds: float = 2.4
    certificate_file: str | None = None
    session_file: str | None = None
    event_endpoint: str | None = None

    @classmethod
    def from_env(cls, env=None):
        e = os.environ if env is None else env
        sid = e['AI_CONTROL_SERVER_ID']
        if not sid.strip() or len(sid) > 256:
            raise ValueError('SERVER_ID_INVALID')
        ca = Path(e['AI_CONTROL_MANAGER_CA_FILE'])
        if not ca.is_absolute() or ca.is_symlink() or not ca.is_file():
            raise ValueError('MANAGER_CA_REQUIRED')
        with ca.open('r') as handle:
            text = handle.read(131073)
        if len(text) > 131072:
            raise ValueError('MANAGER_CA_TOO_LARGE')
        if 'PRIVATE KEY' in text or 'BEGIN CERTIFICATE' not in text:
            raise ValueError('MANAGER_CA_MUST_BE_PUBLIC')
        # The certificate loader validates the PEM; trust material is never fetched from the peer.
        ssl.create_default_context(cafile=str(ca))
        lp = Path(e['AI_CONTROL_LOGIN_BODY_FILE'])
        if not lp.is_absolute() or lp.is_symlink():
            raise ValueError('LOGIN_BODY_FILE_INVALID')
        with lp.open('rb') as f:
            body = loads(f.read(65537))
        required = {'loginId', 'controlPort', 'advertisedIp', 'redis',
                    'bboxStreamKey', 'connectionConfigVersion', 'aiControlCa'}
        if set(body) != required or not isinstance(body['loginId'], str) or not body['loginId']:
            raise ValueError('LOGIN_BODY_CONTRACT')
        if type(body['controlPort']) is not int or not 1 <= body['controlPort'] <= 65535:
            raise ValueError('CONTROL_PORT_INVALID')
        ipaddress.ip_address(body['advertisedIp'])
        # SECUREBRIDGE-04: the issuing Manager CA is also the advertised AI trust anchor.
        try:
            if hashlib.sha256(ssl.PEM_cert_to_DER_cert(body['aiControlCa'])).digest() != hashlib.sha256(ssl.PEM_cert_to_DER_cert(text)).digest():
                raise ValueError('AI_CONTROL_CA_MISMATCH')
        except (TypeError, ValueError):
            raise ValueError('AI_CONTROL_CA_MISMATCH') from None
        if type(body['connectionConfigVersion']) is not int or body['connectionConfigVersion'] < 1:
            raise ValueError('CONNECTION_VERSION_INVALID')
        redis = body['redis']
        if (not isinstance(redis, dict) or set(redis) != {'host','port','db','username','password','tls'}
                or not isinstance(redis['host'], str) or not redis['host']
                or type(redis['port']) is not int or not 1 <= redis['port'] <= 65535
                or type(redis['db']) is not int or not 0 <= redis['db'] <= 15
                or type(redis['tls']) is not bool or not isinstance(body['bboxStreamKey'], str)
                or not body['bboxStreamKey']):
            raise ValueError('LOGIN_CONNECTION_CONTRACT')
        for key in ('username','password'):
            if redis[key] is not None and not isinstance(redis[key], str):
                raise ValueError('LOGIN_CONNECTION_CONTRACT')
        return cls(sid, origin(e['AI_CONTROL_MANAGER_BASE_URL'], 'https'),
                   origin(e.get('AI_CONTROL_RUNTIME_BASE_URL', 'http://analytics:5000'), 'http'),
                   str(ca), secret(e['CONTEXTRA_CONTROL_TOKEN_FILE']),
                   secret(e['CONTEXTRA_RELAY_TOKEN_FILE']), body,
                   # Login passwords are not Bearer tokens: permit spaces, but bound and never log them.
                   read_password(e['AI_CONTROL_PASSWORD_FILE']),
                   certificate_file=e.get('AI_CONTROL_CERTIFICATE_FILE'),
                   session_file=e.get('AI_CONTROL_SESSION_FILE'),
                   event_endpoint=event_target(e.get('AI_CONTROL_EVENT_ENDPOINT'),
                                               origin(e['AI_CONTROL_MANAGER_BASE_URL'], 'https')))


def read_password(path):
    p = Path(path)
    if not p.is_absolute() or p.is_symlink():
        raise ValueError('PASSWORD_FILE_INVALID')
    with p.open('rb') as f:
        raw = f.read(8193)
    value = raw.decode().rstrip('\r\n')
    if not value or len(raw) > 8192 or '\x00' in value:
        raise ValueError('PASSWORD_INVALID')
    return value
