#!/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()
