#!/usr/bin/env python3
import glob
import io
import dataclasses
import pathlib
import sys
import subprocess
import os
import logging
import tomllib

from typing import (Self, Optional, Any,)

logger = logging.getLogger(__name__)

@dataclasses.dataclass
class PyProject:
    dependencies: dict[str, list[str]]
    early_features: Optional[list[str]] = None
    pip_find_links: Optional[list[pathlib.Path]] = None
    runtime_libdirs: Optional[list[pathlib.Path]] = None
    runtime_preload: Optional[list[pathlib.Path]] = None

def pyproject_load(
    d: pathlib.Path,
) -> PyProject:
    with io.open(d, 'rb') as f:
        content = tomllib.load(f)

    assert isinstance(content, dict)

    dependencies : dict[str, list[str]] = dict()

    dependencies['default'] = content['project']['dependencies']

    if (
        'optional-dependencies' in content['project']
    ):
        assert isinstance(
            content['project']['optional-dependencies'],
            dict
        )

        for k, v in content['project']['optional-dependencies'].items():
            assert isinstance(v, list)
            assert isinstance(k, str)

            dependencies[k] = v


    res = PyProject(
        dependencies=dependencies,
    )

    tool_name = 'online.fxreader.pr34'.replace('.', '-')

    if (
        'tool' in content and
        isinstance(
            content['tool'], dict
        ) and
        tool_name in content['tool'] and
        isinstance(
            content['tool'][tool_name],
            dict
        )
    ):
        if 'early_features' in content['tool'][tool_name]:
            res.early_features = content['tool'][tool_name]['early_features']

        if 'pip_find_links' in content['tool'][tool_name]:
            res.pip_find_links = [
                d.parent / pathlib.Path(o)
                for o in content['tool'][tool_name]['pip_find_links']
            ]

        if 'runtime_libdirs' in content['tool'][tool_name]:
            res.runtime_libdirs = [
                d.parent / pathlib.Path(o)
                # pathlib.Path(o)
                for o in content['tool'][tool_name]['runtime_libdirs']
            ]

        if 'runtime_preload' in content['tool'][tool_name]:
            res.runtime_preload = [
                d.parent / pathlib.Path(o)
                # pathlib.Path(o)
                for o in content['tool'][tool_name]['runtime_preload']
            ]

    return res

@dataclasses.dataclass
class BootstrapSettings:
    env_path: pathlib.Path
    python_path: pathlib.Path

    @classmethod
    def get(
        cls,
        base_dir: Optional[pathlib.Path] = None,
    ) -> Self:
        if base_dir is None:
            base_dir = pathlib.Path.cwd()

        env_path = base_dir / '.venv'
        python_path = env_path / 'bin' / 'python3'

        return cls(
            env_path=env_path,
            python_path=python_path,
        )

def env_bootstrap(
    bootstrap_settings: BootstrapSettings,
    pyproject: PyProject,
) -> None:
    pip_find_links : list[pathlib.Path] = []

    if not pyproject.pip_find_links is None:
        pip_find_links.extend(pyproject.pip_find_links)

    pip_find_links_args = sum([
        ['-f', str(o),]
        for o in pip_find_links
    ], [])

    subprocess.check_call([
        'uv', 'venv',
        *pip_find_links_args,
        # '--seed',
        '--offline',
        str(bootstrap_settings.env_path)
    ])

    subprocess.check_call([
        'uv',
        'pip',
        'install',
        *pip_find_links_args,
        '-p',
        bootstrap_settings.python_path,
        '--offline',
        'uv', 'pip',
    ])

    subprocess.check_call([
        bootstrap_settings.python_path,
        '-m',
        'uv', 'pip', 'install',
        *pip_find_links_args,
        '--offline',
        'build', 'setuptools', 'meson-python', 'pybind11',
    ])

    # early_wheels = glob.glob(
    #     str(
    #         pathlib.Path(__file__).parent / 'deps' / 'dist' / 'early' / '*.whl'
    #     )
    # )

    # if len(early_wheels) > 0:
    #     subprocess.check_call([
    #         bootstrap_settings.python_path,
    #         '-m',
    #         'uv', 'pip', 'install',
    #         '--offline',
    #         *early_wheels,
    #     ])

    if pyproject.early_features:
        early_dependencies = sum([
            pyproject.dependencies[o]
            for o in pyproject.early_features
        ], [])

        logger.info(dict(
            early_dependencies=early_dependencies,
        ))

        if len(early_dependencies) > 0:
            subprocess.check_call([
                bootstrap_settings.python_path,
                '-m',
                'uv', 'pip', 'install',
                *pip_find_links_args,
                # '-f', str(pathlib.Path(__file__).parent / 'deps' / 'dist'),
                '--offline',
                *early_dependencies,
            ])

def paths_equal(
    a: pathlib.Path | str,
    b: pathlib.Path | str
) -> bool:
    return (
        os.path.abspath(str(a))  ==
        os.path.abspath(str(b))
    )

def run(
    d: Optional[pathlib.Path] = None,
    cli_path: Optional[pathlib.Path] = None,
) -> None:
    if cli_path is None:
        cli_path = pathlib.Path(__file__).parent / 'cli.py'

    if d is None:
        d = pathlib.Path(__file__).parent / 'pyproject.toml'

    bootstrap_settings = BootstrapSettings.get()

    pyproject : PyProject = pyproject_load(
        d
    )

    logging.basicConfig(level=logging.INFO)

    if not bootstrap_settings.env_path.exists():
        env_bootstrap(
            bootstrap_settings=bootstrap_settings,
            pyproject=pyproject,
        )

    logger.info([sys.executable, sys.argv, bootstrap_settings.python_path])

    if not paths_equal(sys.executable, bootstrap_settings.python_path):
        os.execv(
            str(bootstrap_settings.python_path),
            [
                str(bootstrap_settings.python_path),
                *sys.argv,
            ]
        )

    os.execv(
        str(bootstrap_settings.python_path),
        [
            str(bootstrap_settings.python_path),
            str(
                cli_path
            ),
            *sys.argv[1:],
        ]
    )

if __name__ == '__main__':
    run(
        d=pathlib.Path(__file__).parent / 'python' / 'pyproject.toml',
        cli_path=pathlib.Path(__file__).parent / 'python' / 'cli.py',
    )