#  Copyright (C) 2021
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Mike Orlov <m.orlov@abm-jsc.ru>
#
import logging
import sys
from dataclasses import dataclass
from typing import Callable, Any, Type, TypeVar

from .repr_arg_name_to_exception import repr_arg_name_to_exception
from .._json import read_json_file_by_path
from .._ini import make_ini_from_hint, read_ini_file_by_path
from .._hint import get_hint_from_dataclass
from .._dataclass import FieldErrors, dict_to_dataclass
from .._exception import repr_exception
from .._toml import read_toml_file_by_path

T = TypeVar('T')
logger = logging.getLogger(__file__)


@dataclass
class Arg:
    caster: Callable[[str], Any] = str
    hint: str = None

    def __call__(self, *args, **kwargs):
        return self.caster(*args, **kwargs)

    def __str__(self):
        return self.hint or 'no description'

    @staticmethod
    def json_file(hint: str = None) -> 'Arg':
        return Arg(read_json_file_by_path, hint)

    @staticmethod
    def ini_file_to_dataclass(dataclass_type: Type[T]) -> 'Arg':
        def ini_file_path_to_dataclass(path: str) -> T:
            return dict_to_dataclass(read_ini_file_by_path(path), dataclass_type)
        return Arg(ini_file_path_to_dataclass, hint=make_ini_from_hint(get_hint_from_dataclass(dataclass_type)))

    @staticmethod
    def toml_file_to_dataclass(dataclass_type: Type[T]) -> 'Arg':
        def toml_file_path_to_dataclass(path: str) -> T:
            return dict_to_dataclass(read_toml_file_by_path(path), dataclass_type)
        return Arg(toml_file_path_to_dataclass, hint=make_ini_from_hint(get_hint_from_dataclass(dataclass_type)))

    @staticmethod
    def ini_file(hint: str = None) -> 'Arg':
        return Arg(read_ini_file_by_path, hint)


def parse_args(args: list[str] | None = None, **kwargs: type | Arg) -> list:
    if args is None:
        args = sys.argv[1:]

    usage = f'{sys.argv[0]} {" ".join(kwargs)}'
    full_usage = f'{usage}\nArguments:\n'
    for i, key in enumerate(kwargs):
        full_usage += f'{i+1}. {key}:\n{kwargs[key]}\n'

    arg_name_to_exception = {}
    arg_name_result = {}
    for arg_name, caster in kwargs.items():
        if not args:
            arg_name_to_exception[arg_name] = ValueError("missing argument")
            continue
        arg = args.pop(0)
        try:
            arg_name_result[arg_name] = caster(arg)
        except Exception as e:
            arg_name_to_exception[arg_name] = e

    if arg_name_to_exception:
        logger.warning('Usage:'.upper())
        logger.warning(usage)
        arg_name_to_hint = {
            key: str(type_or_arg) if isinstance(type_or_arg, Arg) else type_or_arg.__qualname__
            for key, type_or_arg in kwargs.items()}
        for line in repr_arg_name_to_exception(arg_name_to_exception, arg_name_to_hint=arg_name_to_hint):
            logger.critical(line)
        exit(2)
    result = [arg_name_result[key] for key in kwargs]
    if len(result) == 1:
        return result[0]
    return result
