from abc import ABCMeta, abstractmethod
from decimal import Decimal
from typing import List, Dict, Optional

import attr

from staff.budget_position.const import FemidaProfessionalLevel
from staff.budget_position.workflow_service.entities.abstract_workflow import AbstractWorkflow
from staff.budget_position.workflow_service.entities.change import Change
from staff.budget_position.workflow_service.entities.types import OccupationId, BudgetPositionCode, GradeId


@attr.s(auto_attribs=True)
class GradeData:
    occupation: OccupationId
    level: Optional[int]  # None соответствует строковому значению 'Без грейда' приходящему из OEBS


@attr.s(auto_attribs=True)
class SalaryData:
    salary: Decimal = attr.ib(converter=Decimal)
    rate: Decimal = attr.ib(converter=Decimal)
    currency: Optional[str] = None


class OEBSError(Exception):
    def __str__(self):
        if len(self.args) == 1:
            return self.args[0]

        return f'{self.__class__.__name__}{self.args}'


class NoResponseOEBSError(OEBSError):
    pass


class UnexpectedResponseOEBSError(OEBSError):
    pass


class OebsErrorResponseOEBSError(UnexpectedResponseOEBSError):
    pass


class WrongJsonResponseOEBSError(UnexpectedResponseOEBSError):
    pass


class EmptyResponseOEBSError(WrongJsonResponseOEBSError):
    pass


class OEBSService(metaclass=ABCMeta):
    """
    Stateless domain service, contains logic for pushing workflows to OEBS.
    This logic does not naturally fit elsewhere
    """
    @abstractmethod
    def push_next_change_to_oebs(self, workflow: AbstractWorkflow, catalyst_login: str) -> None:
        pass

    @abstractmethod
    def update_changes_push_status(self, changes: List[Change]) -> bool:
        pass

    @abstractmethod
    def get_position_as_change(self, budget_position_code: BudgetPositionCode) -> Change:
        pass

    @abstractmethod
    def get_crossing_position_info_as_change(self, budget_position_code: BudgetPositionCode, login: str) -> Change:
        pass

    @abstractmethod
    def get_grades_data(self, logins: List[str]) -> Dict[str, Optional[GradeData]]:
        pass

    @abstractmethod
    def get_grade_id(self, occupation: OccupationId, level: Optional[int]) -> Optional[int]:
        pass

    @abstractmethod
    def get_grade_data(self, grade_id: GradeId) -> Optional[GradeData]:
        pass

    @abstractmethod
    def get_grade_id_by_femida_level(self, occupation: OccupationId, level: FemidaProfessionalLevel) -> Optional[int]:
        pass

    @abstractmethod
    def get_position_link(self, change: Change, login: str, is_write=False) -> str:
        pass

    @abstractmethod
    def get_budget_link(self, change: Change) -> str:
        pass

    @abstractmethod
    def get_salary_data(self, login: str) -> SalaryData:
        pass
