"""Bounded, supervised conversion for anonymous enquiry images only."""
import json
from concurrent.futures import ThreadPoolExecutor, TimeoutError
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import threading
import time

import psutil
from fastapi import HTTPException
from .enquiry_image_worker import MAX_FILE_BYTES, MAX_OUTPUT_BYTES, FORMATS

DECODE_SECONDS = 20
MAX_DECODER_RSS = 768 * 1024 * 1024
POLL_SECONDS = .05
_slots = threading.BoundedSemaphore(2)
_worker = Path(__file__).with_name('enquiry_image_worker.py')


def decode_enquiry_image(raw):
    if not raw or len(raw) > MAX_FILE_BYTES:
        raise HTTPException(413, 'choose an image no larger than 10 MB (10,485,760 bytes).')
    if not _slots.acquire(blocking=False):
        raise HTTPException(503, 'Photo checking is busy. Keep this page open and retry the same enquiry.')
    try:
        with tempfile.TemporaryFile() as output:
            # -I ignores user Python settings; no inherited libheif override
            # can disable decoder limits. Originals travel over stdin only.
            env = {k: os.environ[k] for k in ('SYSTEMROOT', 'WINDIR') if k in os.environ}
            with subprocess.Popen([sys.executable, '-I', str(_worker)],
                                  stdin=subprocess.PIPE, stdout=output,
                                  stderr=subprocess.DEVNULL, env=env) as process:
                monitor = psutil.Process(process.pid)
                deadline = time.monotonic() + DECODE_SECONDS
                # Keep one continuous pipe exchange. Retrying communicate(None)
                # after a short timeout can abandon input not yet written.
                # The separate supervisor still bounds time, memory and output.
                with ThreadPoolExecutor(max_workers=1) as pipe:
                    exchange = pipe.submit(process.communicate, input=raw)
                    try:
                        while True:
                            if time.monotonic() > deadline or output.tell() > MAX_OUTPUT_BYTES + 4096:
                                raise HTTPException(415, 'this image exceeds the local conversion time or output allowance. Choose a smaller still image.')
                            try:
                                if monitor.memory_info().rss > MAX_DECODER_RSS:
                                    raise HTTPException(415, 'this image needs too much memory to convert. Choose a smaller still image.')
                            except psutil.NoSuchProcess:
                                pass
                            try:
                                exchange.result(timeout=POLL_SECONDS)
                                break
                            except TimeoutError:
                                pass
                        if process.returncode != 0:
                            raise HTTPException(415, 'this image could not be safely converted. Choose another still image.')
                    finally:
                        if process.poll() is None:
                            process.kill()
                        process.wait()
            output.seek(0)
            header = output.readline(4096)
            body = output.read(MAX_OUTPUT_BYTES + 1)
        try:
            result = json.loads(header)
            if not result.get('ok'):
                reason = result.get('error')
                messages = {
                    'frames': 'choose a single still image; sequences and multiple main images are not supported.',
                    'dimensions': 'choose an image up to 12 megapixels and 12,000 pixels per side.',
                    'unavailable': 'the local image decoder is unavailable. Keep this page open and ask the owner to check the installation.',
                }
                raise HTTPException(503 if reason == 'unavailable' else 415,
                                    messages.get(reason, 'choose a readable single PNG, JPEG, WebP or HEIC/HEIF still image.'))
            if (result['format'] not in FORMATS or type(result['width']) is not int or type(result['height']) is not int
                    or not 0 < result['width'] <= 1800 or not 0 < result['height'] <= 1800
                    or result['bytes'] != len(body) or not 0 < len(body) <= MAX_OUTPUT_BYTES
                    or not body.startswith(b'\x89PNG\r\n\x1a\n')):
                raise ValueError('Invalid converter result')
        except (ValueError, KeyError, TypeError):
            raise HTTPException(415, 'this image could not be safely converted. Choose another still image.') from None
        return body, result['width'], result['height'], result['format']
    except (OSError, psutil.Error):
        raise HTTPException(503, 'Photo checking is unavailable. Keep this page open and retry the same enquiry.') from None
    finally:
        _slots.release()
