from typing import Any
from typing import Optional
from typing import Type

from pytezos.michelson.micheline import MichelineSequence
from pytezos.michelson.types.base import MichelsonType


class OperationType(MichelsonType, prim='operation'):
    def __init__(self, content: dict, ty: Optional[Type[MichelsonType]] = None):
        super(OperationType, self).__init__()
        self.content = content
        self.ty = ty

    def __repr__(self):
        return self.content['kind']

    def __eq__(self, other):
        if not isinstance(other, OperationType):
            return False
        return self.content == other.content

    @classmethod
    def origination(
        cls,
        source: str,
        script: Type[MichelineSequence],
        storage: MichelsonType,
        balance: int = 0,
        delegate: Optional[str] = None,
    ) -> 'OperationType':
        content = {
            'kind': 'origination',
            'source': source,
            'script': {
                'code': script.as_micheline_expr(),
                'storage': storage.to_micheline_value(),
            },
            'balance': str(balance),
        }
        if delegate is not None:
            content['delegate'] = delegate
        return cls(content, ty=type(storage))

    @classmethod
    def delegation(cls, source: str, delegate: Optional[str] = None) -> 'OperationType':
        content = {
            'kind': 'delegation',
            'source': source,
            'delegate': delegate,
        }
        return cls(content)

    @classmethod
    def transaction(
        cls,
        source: str,
        destination: str,
        amount: int,
        entrypoint: str,
        value: Any,
        param_type: Type[MichelsonType],
    ) -> 'OperationType':
        content = {
            'kind': 'transaction',
            'source': source,
            'destination': destination,
            'amount': str(amount),
            'parameters': {
                'entrypoint': entrypoint,
                'value': value,
            },
        }
        return cls(content, ty=param_type)

    @classmethod
    def event(cls, source: str, event_type: Type[MichelsonType], payload: Any, tag: str) -> 'OperationType':
        content = {
            'kind': 'event',
            'source': source,
            'event_type': event_type.as_micheline_expr(),
            'payload': payload,
            'tag': tag,
        }
        return cls(content, ty=event_type)

    def to_python_object(self, try_unpack=False, lazy_diff=False, comparable=False):
        kind = self.content['kind']
        assert self.ty, f'data type is not defined for {kind}'
        if kind == 'transaction':
            data = self.ty.from_micheline_value(self.content['parameters']['value'])
        elif kind == 'origination':
            data = self.ty.from_micheline_value(self.content['script']['storage'])
        elif kind == 'event':
            data = self.ty.from_micheline_value(self.content['payload'])
        else:
            raise AssertionError(f'not applicable for {kind}')
        return data.to_python_object(try_unpack=try_unpack)
