import os
import ssl
import subprocess
import sys
import time
from contextlib import suppress
from http.client import HTTPResponse
from typing import Callable, Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

CI = os.getenv("CI")


class Compose:
    def __init__(self, project_name: str, env_file: str):
        self.project_name = project_name
        self.base_cmd = (
            "docker",
            "compose",
            "-p",
            project_name,
            "--env-file",
            env_file,
        )

    def __call__(self, *cmd: str) -> None:
        file_args = [
            "-f",
            "compose.yaml",
            "-f",
            "overrides/compose.proxy.yaml",
            "-f",
            "overrides/compose.mariadb.yaml",
            "-f",
            "overrides/compose.redis.yaml",
        ]
        if CI:
            file_args += ("-f", "tests/compose.ci.yaml")

        args = self.base_cmd + tuple(file_args) + cmd
        subprocess.check_call(args)

    def exec(self, *cmd: str) -> None:
        if sys.stdout.isatty():
            self("exec", *cmd)
        else:
            self("exec", "-T", *cmd)

    def stop(self) -> None:
        # Stop all containers in `test` project if they are running.
        # We don't care if it fails.
        with suppress(subprocess.CalledProcessError):
            subprocess.check_call(self.base_cmd + ("down", "-v", "--remove-orphans"))

    def bench(self, *cmd: str) -> None:
        self.exec("backend", "bench", *cmd)


def check_url_content(
    url: str, callback: Callable[[str], Optional[str]], site_name: str
):
    for _ in range(100):
        try:
            response = wait_for_url(url=url, site_name=site_name, attempts=1)
        except RuntimeError:
            pass
        else:
            text: str = response.read().decode()
            ret = callback(text)
            if ret:
                print(ret)
                return

        time.sleep(0.1)

    raise RuntimeError(f"Couldn't verify expected content from {url}")


def wait_for_url(url: str, site_name: str, attempts: int = 100) -> HTTPResponse:
    request = Request(url, headers={"Host": site_name})

    # This is needed to check https override
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE

    for _ in range(attempts):
        try:
            return urlopen(request, context=ctx)
        except HTTPError as exc:
            if exc.code not in (404, 502):
                raise
        except URLError:
            pass

        time.sleep(0.1)

    raise RuntimeError(f"Couldn't ping {url}")
