# Logic is as follows:
# 1. Download cairo source code: https://cairographics.org/releases/cairo-<version>.tar.xz
# 2. Verify the downloaded file using the sha256sums file: https://cairographics.org/releases/cairo-<version>.tar.xz.sha256sum
# 3. Extract the downloaded file.
# 4. Create a virtual environment and install meson and ninja.
# 5. Run meson build in the extracted directory. Also, set required prefix.
# 6. Run meson compile -C build.
# 7. Run meson install -C build.

import hashlib
import logging
import os
import subprocess
import sys
import tarfile
import tempfile
import urllib.request
from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path
from sys import stdout

CAIRO_VERSION = "1.18.0"
CAIRO_URL = f"https://cairographics.org/releases/cairo-{CAIRO_VERSION}.tar.xz"
CAIRO_SHA256_URL = f"{CAIRO_URL}.sha256sum"

VENV_NAME = "meson-venv"
BUILD_DIR = "build"
INSTALL_PREFIX = Path(__file__).parent.parent.parent / "third_party" / "cairo"

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)


def is_ci():
    return os.getenv("CI", None) is not None


def download_file(url, path):
    logger.info(f"Downloading {url} to {path}")
    block_size = 1024 * 1024
    with urllib.request.urlopen(url) as response, open(path, "wb") as file:
        while True:
            data = response.read(block_size)
            if not data:
                break
            file.write(data)


def verify_sha256sum(path, sha256sum):
    with open(path, "rb") as file:
        file_hash = hashlib.sha256(file.read()).hexdigest()
    if file_hash != sha256sum:
        raise Exception("SHA256SUM does not match")


def extract_tar_xz(path, directory):
    with tarfile.open(path) as file:
        file.extractall(directory)


def run_command(command, cwd=None, env=None):
    process = subprocess.Popen(command, cwd=cwd, env=env)
    process.communicate()
    if process.returncode != 0:
        raise Exception("Command failed")


@contextmanager
def gha_group(title: str) -> Generator:
    if not is_ci():
        yield
        return
    print(f"\n::group::{title}")
    stdout.flush()
    try:
        yield
    finally:
        print("::endgroup::")
        stdout.flush()


def set_env_var_gha(name: str, value: str) -> None:
    if not is_ci():
        return
    env_file = os.getenv("GITHUB_ENV", None)
    if env_file is None:
        return
    with open(env_file, "a") as file:
        file.write(f"{name}={value}\n")
    stdout.flush()


def get_ld_library_path(prefix: Path) -> str:
    # given a prefix, the ld library path can be found at
    # <prefix>/lib/* or sometimes just <prefix>/lib
    # this function returns the path to the ld library path

    # first, check if the ld library path exists at <prefix>/lib/*
    ld_library_paths = list(prefix.glob("lib/*"))
    if len(ld_library_paths) == 1:
        return ld_library_paths[0].absolute().as_posix()

    # if the ld library path does not exist at <prefix>/lib/*,
    # return <prefix>/lib
    ld_library_path = prefix / "lib"
    if ld_library_path.exists():
        return ld_library_path.absolute().as_posix()
    return ""


def main():
    if sys.platform == "win32":
        logger.info("Skipping build on windows")
        return

    with tempfile.TemporaryDirectory() as tmpdir:
        with gha_group("Downloading and Extracting Cairo"):
            logger.info(f"Downloading cairo version {CAIRO_VERSION}")
            download_file(CAIRO_URL, os.path.join(tmpdir, "cairo.tar.xz"))

            logger.info("Downloading cairo sha256sum")
            download_file(CAIRO_SHA256_URL, os.path.join(tmpdir, "cairo.sha256sum"))

            logger.info("Verifying cairo sha256sum")
            with open(os.path.join(tmpdir, "cairo.sha256sum")) as file:
                sha256sum = file.read().split()[0]
            verify_sha256sum(os.path.join(tmpdir, "cairo.tar.xz"), sha256sum)

            logger.info("Extracting cairo")
            extract_tar_xz(os.path.join(tmpdir, "cairo.tar.xz"), tmpdir)

        with gha_group("Installing meson and ninja"):
            logger.info("Creating virtual environment")
            run_command([sys.executable, "-m", "venv", os.path.join(tmpdir, VENV_NAME)])

            logger.info("Installing meson and ninja")
            run_command(
                [
                    os.path.join(tmpdir, VENV_NAME, "bin", "pip"),
                    "install",
                    "meson",
                    "ninja",
                ]
            )

        # Inherit the current environment so PKG_CONFIG_PATH, CFLAGS, LDFLAGS, etc. are preserved.
        env_vars = os.environ.copy()
        # Prepend the venv bin directory so meson/ninja from the venv are used.
        env_vars["PATH"] = f"{os.path.join(tmpdir, VENV_NAME, 'bin')}{os.pathsep}{env_vars.get('PATH','')}"

        # Ensure Homebrew-provided pkgconfig and include/lib paths are present on macOS ARM.
        if sys.platform == "darwin":
            try:
                # Try to get specific prefix for lzo (safer for opt path), fall back to generic brew prefix.
                brew_prefix = subprocess.check_output(["brew", "--prefix", "lzo"], text=True).strip()
            except subprocess.CalledProcessError:
                try:
                    brew_prefix = subprocess.check_output(["brew", "--prefix"], text=True).strip()
                except Exception:
                    brew_prefix = None

            if brew_prefix:
                # pkg-config files can live in lib/pkgconfig or opt/<pkg>/lib/pkgconfig
                pkgconfig_paths = [f"{brew_prefix}/lib/pkgconfig", f"{brew_prefix}/opt/lzo/lib/pkgconfig"]
                # merge with any existing PKG_CONFIG_PATH
                existing_pc = env_vars.get("PKG_CONFIG_PATH", "")
                merged_pc = ":".join([p for p in pkgconfig_paths if p]) + (f":{existing_pc}" if existing_pc else "")
                env_vars["PKG_CONFIG_PATH"] = merged_pc

                # Ensure compiler & linker flags include brew include/lib
                existing_cflags = env_vars.get("CFLAGS", "")
                existing_ldflags = env_vars.get("LDFLAGS", "")
                env_vars["CFLAGS"] = f"-I{brew_prefix}/include {existing_cflags}".strip()
                env_vars["LDFLAGS"] = f"-L{brew_prefix}/lib {existing_ldflags}".strip()

        # Debugging: log environment keys relevant to detection
        # logger.info(f"env vars for meson: {env_vars}")

        with gha_group("Building and Installing Cairo"):
            logger.info("Running meson setup")
            run_command(
                [
                    os.path.join(tmpdir, VENV_NAME, "bin", "meson"),
                    "setup",
                    BUILD_DIR,
                    f"--prefix={INSTALL_PREFIX.absolute().as_posix()}",
                    "--buildtype=release",
                    "-Dtests=disabled",
                ],
                cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"),
                env=env_vars,
            )

            logger.info("Running meson compile")
            run_command(
                [
                    os.path.join(tmpdir, VENV_NAME, "bin", "meson"),
                    "compile",
                    "-C",
                    BUILD_DIR,
                ],
                cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"),
                env=env_vars,
            )

            logger.info("Running meson install")
            run_command(
                [
                    os.path.join(tmpdir, VENV_NAME, "bin", "meson"),
                    "install",
                    "-C",
                    BUILD_DIR,
                ],
                cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"),
                env=env_vars,
            )

        logger.info(f"Successfully built cairo and installed it to {INSTALL_PREFIX}")


if __name__ == "__main__":
    if "--set-env-vars" in sys.argv:
        with gha_group("Setting environment variables"):
            # append the pkgconfig directory to PKG_CONFIG_PATH
            set_env_var_gha(
                "PKG_CONFIG_PATH",
                f"{Path(get_ld_library_path(INSTALL_PREFIX), 'pkgconfig').as_posix()}{os.pathsep}"
                f'{os.getenv("PKG_CONFIG_PATH", "")}',
            )
            set_env_var_gha(
                "LD_LIBRARY_PATH",
                f"{get_ld_library_path(INSTALL_PREFIX)}{os.pathsep}"
                f'{os.getenv("LD_LIBRARY_PATH", "")}',
            )
        sys.exit(0)
    main()
