import sys
import shutil
import glob
import io
import copy
import subprocess
import pathlib
import logging
import enum
import argparse
import dataclasses

from typing import (
	Optional,
	# override,
)

from typing_extensions import (
	override,
)

from online.fxreader.pr34.commands_typed.logging import setup as logging_setup

from online.fxreader.pr34.commands_typed import cli as _cli

from online.fxreader.pr34.commands_typed import cli_bootstrap

logging_setup()

logger = logging.getLogger(__name__)


class Command(enum.Enum):
	mypy = 'mypy'
	pyright = 'pyright'
	ruff = 'ruff'
	deploy_wheel = 'deploy:wheel'
	tests = 'tests'
	meson_setup = 'meson:setup'
	module_switch = 'module:switch'
	pyrefly = 'pyrefly'


@dataclasses.dataclass
class Settings(
	_cli.DistSettings,
):
	base_dir: pathlib.Path = pathlib.Path(__file__).parent.parent
	build_dir: pathlib.Path = base_dir / 'tmp' / 'build'
	wheel_dir: pathlib.Path = base_dir / 'deps' / 'dist'
	env_path: pathlib.Path = cli_bootstrap.BootstrapSettings.get().env_path
	python_path: pathlib.Path = pathlib.Path(sys.executable)


class CLI(_cli.CLI):
	def __init__(self) -> None:
		self.settings = Settings()
		self._projects: dict[str, _cli.Project] = {
			'online.fxreader.pr34': _cli.Project(
				source_dir=self.settings.base_dir / 'python',
				build_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'build',
				dest_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'install',
				meson_path=self.settings.base_dir / 'python' / 'meson.build',
			),
			'online.fxreader.pr34.commands_typed.archlinux': _cli.Project(
				source_dir=self.settings.base_dir / 'meson' / 'online' / 'fxreader' / 'pr34' / 'commands_typed' / 'archlinux',
				build_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'commands_typed' / 'archlinux' / 'build',
				dest_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'commands_typed' / 'archlinux' / 'install',
				meson_path=self.settings.base_dir / 'meson' / 'online' / 'fxreader' / 'pr34' / 'commands_typed' / 'archlinux' / 'meson.build',
			),
		}

		self._dependencies: dict[str, _cli.Dependency] = dict()

	@override
	@property
	def dist_settings(self) -> _cli.DistSettings:
		return self.settings

	@override
	@property
	def projects(self) -> dict[str, _cli.Project]:
		return self._projects

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

		project = self._projects['online.fxreader.pr34']

		_mypy.run(
			argv,
			settings=_mypy.MypySettings(
				paths=[
					# Settings.settings().project_root / 'dotfiles/.local/bin/commands',
					# project.source_dir / 'm.py',
					project.source_dir / '_m.py',
					project.source_dir / 'online',
					project.source_dir / 'cli.py',
					project.source_dir / 'm.py',
					# Settings.settings().project_root / 'deps/com.github.aiortc.aiortc/src',
					# Settings.settings().project_root / 'm.py',
				],
				max_errors={
					'online/fxreader/pr34/commands_typed': 0,
					# 'online/fxreader/pr34/commands': 0,
					'cli.py': 0,
					'm.py': 0,
					'../deps/com.github.aiortc.aiortc/src/online_fxreader': 0,
					'../deps/com.github.aiortc.aiortc/src/aiortc/contrib/signaling': 0,
				},
			),
		)

	@override
	@property
	def dependencies(self) -> dict[str, _cli.Dependency]:
		return self._dependencies

	def run(self, argv: Optional[list[str]] = None) -> None:
		if argv is None:
			argv = copy.deepcopy(sys.argv)

		parser = argparse.ArgumentParser()
		parser.add_argument('command', choices=[o.value for o in Command])
		parser.add_argument('-p', '--project', choices=[o for o in self.projects])
		parser.add_argument(
			'-o',
			'--output_dir',
			default=None,
			help='wheel output dir for deploy:wheel',
		)
		parser.add_argument(
			'-f',
			'--force',
			default=False,
			action='store_true',
			help='remove install dir, before installing, default = false',
		)

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

		default_project: Optional[str] = None

		for k, v in self.projects.items():
			if cli_bootstrap.paths_equal(
				v.source_dir.resolve(),
				# pathlib.Path(__file__).parent.resolve(),
				pathlib.Path.cwd(),
			):
				default_project = k

		if options.project is None:
			if not default_project is None:
				options.project = default_project
			else:
				logger.error(dict(msg='not provided project name'))
				raise NotImplementedError

		options.command = Command(options.command)

		if options.command is Command.deploy_wheel:
			assert not options.project is None

			self.deploy_wheel(
				project_name=options.project,
				argv=args,
				output_dir=options.output_dir,
				# mypy=True,
				ruff=True,
				pyright=False,
				pyrefly=True,
			)
		elif options.command is Command.pyright:
			self.pyright(
				project_name=options.project,
				argv=args,
			)
		elif options.command is Command.ruff:
			self.ruff(
				project_name=options.project,
				argv=args,
			)
		elif options.command is Command.meson_setup:
			assert not options.project is None

			self.meson_setup(
				project_name=options.project,
				argv=args,
				force=options.force,
			)
		elif options.command is Command.mypy:
			self.mypy(
				argv=args,
			)
		elif options.command is Command.tests:
			from online.fxreader.pr34.commands_typed import argparse as pr34_argparse

			tests_parser = argparse.ArgumentParser()
			tests_parser.add_argument(
				'--timeout',
				default=16,
				type=int,
				help='test timeout in seconds, default = 16',
			)

			tests_options, tests_args = pr34_argparse.parse_args(tests_parser, args)

			for k, v in self.projects.items():
				if len(tests_args) > 0:
					cmd = [
						sys.executable,
						'-m',
						'unittest',
						*tests_args,
					]
				else:
					cmd = [
						sys.executable,
						'-m',
						'unittest',
						'discover',
						'-s',
						str(v.source_dir),
						'-p',
						'test_*.py',
						'-t',
						str(v.source_dir),
					]

				subprocess.check_call(
					cmd,
					cwd=str(v.source_dir),
					timeout=tests_options.timeout,
				)
		elif options.command is Command.module_switch:
			assert not options.project is None

			self.module_switch(
				project_name=options.project,
				argv=args,
			)
		elif options.command is Command.pyrefly:
			assert not options.project is None

			self.pyrefly(
				project_name=options.project,
				argv=args,
			)
		else:
			raise NotImplementedError


if __name__ == '__main__':
	CLI().run()
