import json
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

from .exceptions import EngineClientError, EngineConnectionError, EngineResponseError


SDK_VERSION = '1.0.0'


class EngineClient:
    """Small, versioned boundary around the private image engine API."""

    def __init__(self, base_url, timeout=12):
        self.base_url = base_url.rstrip('/')
        self.timeout = timeout

    def _request(self, method, path, payload=None, timeout=None):
        data = None
        headers = {'Accept': 'application/json', 'X-Engine-SDK-Version': SDK_VERSION}
        if payload is not None:
            data = json.dumps(payload, ensure_ascii=False).encode('utf-8')
            headers['Content-Type'] = 'application/json'
        request = Request(
            f'{self.base_url}{path}',
            data=data,
            headers=headers,
            method=method,
        )
        try:
            with urlopen(request, timeout=timeout or self.timeout) as result:
                raw = result.read()
                response = json.loads(raw.decode('utf-8'))
        except HTTPError as error:
            detail = error.read().decode('utf-8', 'replace')[:500]
            raise EngineResponseError(f'engine_http_{error.code}: {detail}') from error
        except (URLError, TimeoutError, OSError) as error:
            raise EngineConnectionError(f'engine_unreachable: {error}') from error
        except (UnicodeDecodeError, json.JSONDecodeError) as error:
            raise EngineResponseError('engine_invalid_json') from error
        if not isinstance(response, dict):
            raise EngineResponseError('engine_response_must_be_object')
        return response

    def health(self):
        """Return the engine health payload."""
        return self._request('GET', '/api/health', timeout=min(self.timeout, 3))

    def list_presets(self):
        """Return the engine-owned, versioned preset catalog."""
        return self._request('GET', '/api/presets')

    def capabilities(self):
        """Return engine capabilities, including its current preset catalog."""
        return self._request('GET', '/api/capabilities')

    def status(self):
        """Return a normalized status object for dashboard health checks."""
        try:
            health = self.health()
            return {'connected': True, 'url': self.base_url, 'health': health}
        except EngineClientError as error:
            return {'connected': False, 'url': self.base_url, 'error': str(error)}

    def generate(self, prompt, colors=None, reference_profile=None, **options):
        """Generate one or more assets through the engine contract."""
        payload = {'prompt': prompt, 'colors': colors, 'referenceProfile': reference_profile}
        payload.update(options)
        generated = self._request('POST', '/api/generate', payload)
        asset = generated.get('asset')
        if generated.get('status') not in (None, 'created') or not isinstance(asset, dict):
            raise EngineResponseError('engine_generation_missing_asset')
        return generated
