#!/usr/bin/env python3
#vim: set filetype=python

import logging
import json
import enum
import pathlib
import sys
import argparse
#import optparse
import dataclasses
import subprocess
import os



from typing import (
    Optional, Any, TypeAlias, Literal, cast, BinaryIO, Generator,
    ClassVar, Self,
)

logger = logging.getLogger()

@dataclasses.dataclass
class Settings:
    project_root : pathlib.Path = pathlib.Path.cwd()

    env_path : pathlib.Path = project_root / 'tmp' / 'env3'

    _settings : ClassVar[Optional['Settings']] = None

    @classmethod
    def settings(cls) -> Self:
        if cls._settings is None:
            cls._settings = cls()

        return cls._settings

def js(argv: list[str]) -> int:
    return subprocess.check_call([
        'sudo',
        'docker-compose',
        '--project-directory',
        Settings.settings().project_root,
        '-f',
        Settings.settings().project_root / 'docker' / 'js' / 'docker-compose.yml',
        *argv,
    ])

def env(
    argv: Optional[list[str]] = None,
    mode: Literal['exec', 'subprocess'] = 'subprocess',
    **kwargs: Any,
) -> Optional[subprocess.CompletedProcess[bytes]]:
    env_path = Settings.settings().env_path

    if not env_path.exists():
        subprocess.check_call([
            sys.executable, '-m', 'venv',
            '--system-site-packages',
            str(env_path)
        ])

        subprocess.check_call([
            env_path / 'bin' / 'python3',
            '-m', 'pip',
            'install', '-r', 'requirements.txt',
        ])

    if not argv is None:
        python_path = str(env_path / 'bin' / 'python3')

        if mode == 'exec':
            os.execv(
                python_path,
                [
                    python_path,
                    *argv,
                ],
            )
            return None
        elif mode == 'subprocess':
            return subprocess.run([
                python_path,
                *argv,
            ], **kwargs)
        else:
            raise NotImplementedError

    return None

def ruff(argv: list[str]) -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-i',
        dest='paths',
        help='specify paths to check',
        default=[],
        action='append',
    )
    parser.add_argument(
        '-e',
        dest='exclude',
        help='rules to ignore',
        default=[],
        action='append',
    )

    options, args = parser.parse_known_args(argv)

    if len(options.paths) == 0:
        options.paths.extend([
            '.',
            'dotfiles/.local/bin/commands',
        ])

    if len(options.exclude) == 0:
        options.exclude.extend([
            'E731',
            'E713',
            'E714',
            'E703',
        ])

    res = env([
        '-m',
        'ruff',
        'check',
        *args,
        '--output-format', 'json',
        '--ignore', ','.join(options.exclude),
        *options.paths,
    ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    assert not res is None

    errors = json.loads(res.stdout.decode('utf-8'))

    g: dict[str, Any] = dict()
    for o in errors:
        if not o['filename'] in g:
            g[o['filename']] = []
        g[o['filename']].append(o)

    h = {
        k : len(v)
        for k, v in g.items()
    }

    logger.info(json.dumps(errors, indent=4))
    logger.info(json.dumps(h, indent=4))


def inside_env() -> bool:
    try:
        import numpy
        return True
    except Exception:
        return False

#class Commands(enum.StrEnum):
#    js = 'js'
#    mypy = 'mypy'
#    env = 'env'
#    ruff = 'ruff'
#    m2 = 'm2'

# def mypy(argv: list[str]) -> None:
#     import online.fxreader.pr34.commands_typed.mypy as _mypy

#     _mypy.run(
#         argv,
#     )

def host_deps(argv: list[str]) -> None:
    if sys.platform in ['linux']:
        subprocess.check_call(r'''
            exec yay -S $(cat requirements-archlinux.txt)
        ''', shell=True,)
    else:
        raise NotImplementedError

Command_args = ['js', 'mypy', 'env', 'ruff', 'm2', 'host_deps',]

Command : TypeAlias = Literal['js', 'mypy', 'env', 'ruff', 'm2', 'host_deps',]

def run(argv: Optional[list[str]] = None) -> None:
    logging.basicConfig(
        level=logging.INFO,
        format=(
            '%(levelname)s:%(name)s:%(message)s'
            ':%(process)d'
            ':%(asctime)s'
            ':%(pathname)s:%(funcName)s:%(lineno)s'
        ),
    )

    if argv is None:
        argv = sys.argv[:]


    parser = argparse.ArgumentParser()
    parser.add_argument(
        'command',
        #'_command',
        choices=[
            o
            for o in Command_args
        ],
        #required=True,
    )

    options, args = parser.parse_known_args(argv[1:])

    assert options.command in Command_args

    if len(args) > 0 and args[0] == '--':
        del args[0]

    #options.command = Commands(options._command)

    if options.command == 'js':
        js(args)
    elif options.command == 'host_deps':
        host_deps(args)
    elif options.command == 'env':
        env(args, mode='exec',)
    # elif options.command == 'mypy':
    #     if not inside_env():
    #         env(
    #             [
    #                 pathlib.Path(__file__).parent / 'm.py',
    #                 *argv[1:],
    #             ],
    #             mode='exec'
    #         )
    #     else:
    #         mypy(args)
    elif options.command == 'ruff':
        ruff(args)
    elif options.command == 'm2':
        if not inside_env():
            env(['--', '_m.py', 'm2', *args])
            return

        import python.tasks.cython
        python.tasks.cython.mypyc_build(
            pathlib.Path('_m.py')
        )
    else:
        raise NotImplementedError

if __name__ == '__main__':
    run()