import asyncio
import mimetypes
import re
import statistics
import sys
import time
from pathlib import Path

import aiohttp
import pytest
from PIL import Image
from clarabot_nano_common.utility.path.java import java_compatible_absolute_path

from clarabot_nano.indexer.extractor.picoture import PicotureExtraction
from clarabot_nano.picoture import PICOTURE_JAR
from clarabot_nano.utility.algorithm import decode_interleaved_int
from clarabot_nano_tests.picoture.resources import INPUT_ROOT, OUTPUT_ROOT
from clarabot_nano_tests.testing.file import walk_dir_for_relative_paths
from clarabot_nano_tests.testing.process import popen


@pytest.mark.asyncio
async def test_picoture_output(dev_nano_root_dir):
    assert PICOTURE_JAR.is_file(), str(PICOTURE_JAR)
    java_exe = Path(dev_nano_root_dir, "deps", "jre", "bin", "java.exe" if sys.platform.startswith("win") else "java")
    assert java_exe.is_file(), str(java_exe)
    file_paths = set(walk_dir_for_relative_paths(INPUT_ROOT))
    for out_path in walk_dir_for_relative_paths(OUTPUT_ROOT):
        assert out_path.endswith("_p.jpg") or out_path.endswith("_t.jpg")
        assert out_path.rpartition("_")[0] in file_paths, out_path
    port = 7072
    version_re = re.compile(r"^Picoture \d+\.\d+\.\d+$")
    version_url = f"http://127.0.0.1:{port}/version"
    picture_url = f"http://127.0.0.1:{port}/pictures"
    async with aiohttp.ClientSession() as session:
        with popen(
            [
                java_compatible_absolute_path(java_exe),
                "-jar",
                java_compatible_absolute_path(PICOTURE_JAR),
                "--host",
                "127.0.0.1",
                "--port",
                str(port),
            ]
        ) as proc:
            # wait for picoture to start
            for _ in range(50):
                assert proc.poll() is None
                try:
                    async with session.get(version_url) as resp:
                        if resp.status == 200:
                            text = await resp.text()
                            assert version_re.match(text), text
                            break
                except aiohttp.ClientConnectorError:
                    pass
                await asyncio.sleep(0.25)
            else:
                assert False, "Picoture did not start in time"
            # check or generate reference output
            times = []
            for file_path in sorted(file_paths):
                in_path = INPUT_ROOT / file_path
                preview_path = OUTPUT_ROOT / f"{file_path}_p.jpg"
                thumbnail_path = OUTPUT_ROOT / f"{file_path}_t.jpg"
                t = time.perf_counter()
                headers = {}
                mime_type = mimetypes.guess_type(in_path)[0]
                if mime_type:
                    headers["Content-Type"] = mime_type
                async with session.post(picture_url, data=in_path.read_bytes(), headers=headers) as resp:
                    times.append(time.perf_counter() - t)
                    generated_output = await resp.read()
                    ex = PicotureExtraction()
                    if resp.status == 200:
                        ex.set_result(generated_output)
                        assert ex.get_preview() and ex.get_thumbnail() and ex.get_preview_size()
                    elif resp.status == 415:  # unsupported media type
                        assert generated_output == b"", generated_output
                    else:
                        assert False, f"Unexpected status: {resp.status}\n{generated_output}\n{in_path}"
                    for out_path, out_data, out_size in [
                        (preview_path, ex.get_preview() or b"", ex.get_preview_size()),
                        (thumbnail_path, ex.get_thumbnail() or b"", None),
                    ]:
                        if out_path.exists():
                            if out_data != out_path.read_bytes():
                                (out_path.parent / f"ERR_{out_path.name}").write_bytes(out_data)
                                assert False, f"Mismatching generated and reference data: {file_path}"
                        else:
                            out_path.parent.mkdir(parents=True, exist_ok=True)
                            out_path.write_bytes(out_data)
                        if out_size is not None:
                            out_wh = decode_interleaved_int(out_size)
                            im = Image.open(out_path)
                            assert out_wh == im.size, f"{out_size} {out_wh} {im.size}"
                            im.close()
    # some performance checking
    assert statistics.median(times) < 0.1, str(times)
    assert 0.5 < max(times) < 8.0, str(times)  # 4K webp is very slow
