#  Copyright (C) 2025
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Mike Orlov <m.orlov@abm-jsc.ru>
import abc
import functools
import json
from dataclasses import dataclass, field
from typing import Callable, Mapping, Iterable, ClassVar

import http_tools
import yarl
from frozendict import frozendict
from init_helpers import custom_dumps, Jsonable, DataclassProtocol
from more_itertools import first

from .spec_ref import SpecRef
from .spec_resource import SpecResource


@dataclass(frozen=True, slots=True)
class BaseExample(SpecResource, abc.ABC):
    name: str = field(default='', kw_only=True)
    summary: str = field(default='', kw_only=True)
    description: str = field(default='', kw_only=True)

    def __init__(self, *, name: str = None, summary: str = '', description: str = ''):
        object.__setattr__(self, 'name', name)
        object.__setattr__(self, 'summary', summary)
        object.__setattr__(self, 'description', description)

    def get_spec_dependencies(self) -> frozenset[SpecResource]:
        return frozenset()

    @abc.abstractmethod
    def get_spec_dict(self, dependency_to_ref: Mapping['SpecResource', SpecRef]) -> frozendict[str, Jsonable]:
        result = {}
        result |= {'summary': self.summary} if self.summary else {}
        result |= {'description': self.description} if self.description else {}
        return frozendict(result)

    def get_name(self, i: int):
        return self.name or f'Example {i}'

    @functools.cache
    def _get_repr_parts(self) -> tuple[str, ...]:
        result = []
        result += [f'name={self.name!r}'] if self.name else []
        result += [f'summary={self.summary!r}'] if self.summary else []
        result += [f'description={self.description!r}'] if self.description else []
        return tuple(result)

    def __repr__(self):
        return f'{self.__class__.__name__}({", ".join(self._get_repr_parts())})'

    __str__ = __repr__


@dataclass(frozen=True, slots=True)
class Example(BaseExample):
    jsoner: ClassVar[Callable[[Jsonable], str]] = staticmethod(custom_dumps)
    jsoned: str = field(init=False)

    def __init__(self, val: Jsonable | DataclassProtocol, *, name: str = None, summary: str = '', description: str = ''):
        kwargs = {}
        kwargs |= {'name': name} if name else {}
        kwargs |= {'summary': summary} if summary else {}
        kwargs |= {'description': description} if description else {}
        BaseExample.__init__(self, **kwargs)
        jsoned = self.jsoner(val)
        object.__setattr__(self, 'jsoned', jsoned)

    def get_spec_dict(self, dependency_to_ref: Mapping['SpecResource', SpecRef]) -> frozendict[str, Jsonable]:
        result = {"value": self.value}
        return frozendict(BaseExample.get_spec_dict(self, dependency_to_ref) | result)

    @property
    def value(self) -> Jsonable:
        return json.loads(self.jsoned)

    def _get_repr_parts(self) -> tuple[str, ...]:
        return (f"value={self.value!r}", ) + BaseExample._get_repr_parts(self)

    def __repr__(self):
        return f'{self.__class__.__name__}({", ".join(self._get_repr_parts())})'

    __str__ = __repr__

# @dataclass(frozen=True)
# class RawExample(Example):
#     def __post_init__(self, val: Jsonable):
#         object.__setattr__(self, 'jsoned', val)


@dataclass(frozen=True, slots=True)
class AnswerExample(Example):
    def __init__(self, answer: http_tools.Answer, *, name: str = None, summary: str = '', description: str = ''):
        kwargs = {}
        kwargs |= {'name': name} if name else {}
        kwargs |= {'summary': summary} if summary else {}
        kwargs |= {'description': description} if description else {}
        Example.__init__(self, json.loads(answer.body.decode()), **kwargs)
        if answer.content_type != http_tools.ContentType.Json:
            raise TypeError(f"Only Json is supported, got: {answer.content_type}")

    def _get_repr_parts(self) -> tuple[str, ...]:
        return (f"value={json.dumps(self.value)}", ) + BaseExample._get_repr_parts(self)

    def __repr__(self):
        return f'{self.__class__.__name__}({", ".join(self._get_repr_parts())})'

    __str__ = __repr__


@dataclass(frozen=True, slots=True)
class UrlExample(BaseExample):
    url: yarl.URL

    def get_spec_dict(self, dependency_to_ref: Mapping['SpecResource', SpecRef]) -> frozendict[str, Jsonable]:
        result = {"externalValue": str(self.url)}
        return frozendict(BaseExample.get_spec_dict(self, dependency_to_ref) | result)

    @functools.cache
    def _get_repr_parts(self) -> tuple[str, ...]:
        return (f"url={self.url!r}", ) + BaseExample._get_repr_parts(self)

    def __repr__(self):
        return f'{self.__class__.__name__}({", ".join(self._get_repr_parts())})'

    __str__ = __repr__


def get_examples_as_dict(
        examples: Iterable[BaseExample],
        dependency_to_ref: Mapping['SpecResource', SpecRef]
) -> frozendict[str, Jsonable]:
    result = {}
    if examples:
        if example := first((e for e in examples if isinstance(e, Example)), default=None):
            result['example'] = example.value
        result['examples'] = {e.get_name(i): dependency_to_ref[e] for i, e in enumerate(examples, 1)}
    return frozendict(result)
