"""Models used by the client"""
import datetime
import json
from enum import Enum
from typing import Any, Dict, List, Optional, Type, TypeVar
import pydantic


def to_lower_camel(s: str) -> str:
    """Alias generator to avoid breaking PEP8"""
    first, *others = s.split("_")
    return "".join([first.lower(), *map(str.capitalize, others)])


T = TypeVar("T", bound="BaseModel")


class BaseModel(pydantic.BaseModel):
    """Expanded BaseModel for convenience"""

    @classmethod
    def from_dict(cls: Type[T], data: Dict[Any, Any]) -> T:
        """Initialize class from dict"""
        return cls(**data)

    @classmethod
    def from_json(cls: Type[T], json_data: str) -> T:
        """Initialize class from json file"""
        data = json.loads(json_data)
        return cls.from_dict(data)


class BatchProgressEnum(str, Enum):
    CREATED = "created"
    VALIDATION_COMPLETED = "validation_completed"
    VALIDATION_FAILED = "validation_failed"
    SENT_TO_UBW = "sent_to_ubw"
    SEND_TO_UBW_FAILED = "send_to_ubw_failed"
    POLLING_COMPLETED = "polling_completed"
    POLLING_FAILED = "polling_failed"
    UBW_IMPORT_OK = "ubw_import_ok"
    UBW_IMPORT_FAILED = "ubw_import_failed"
    FETCH_FINAL_VOUCHERNO_COMPLETED = "fetch_final_voucherno_completed"
    FETCH_FINAL_VOUCHERNO_FAILED = "fetch_final_voucherno_failed"

    def __str__(self) -> str:
        return str(self.value)


class ResponseStatusEnum(str, Enum):
    ACCEPTED = "Accepted"
    CONFLICT = "Conflict"
    UNKNOWN = "Unknown"

    def __str__(self) -> str:
        return str(self.value)


class Transaction(BaseModel):
    account: str
    amount: float
    transdate: Optional[datetime.datetime]
    curamount: Optional[float]
    currency: Optional[str]
    description: str
    dim1: Optional[str]
    dim2: Optional[str]
    dim3: Optional[str]
    dim4: Optional[str]
    dim5: Optional[str]
    dim6: Optional[str]
    dim7: Optional[str]
    sequenceno: int
    taxcode: Optional[str]
    transtype: Optional[str]
    extinvref: Optional[str]


class Voucher(BaseModel):
    voucherdate: datetime.datetime
    exref: Optional[str]
    voucherno: int
    transactions: List[Transaction]


class InputBatch(BaseModel):
    """
    Model representing a batch, with a list of vouchers (and each
    voucher has a list of transactions.)
    """

    client: str
    batchid: str
    period: Optional[int]
    interface: str
    vouchertype: Optional[str]
    vouchers: List[Voucher]


class OutputBatch(BaseModel):
    """
    Model representing a batch, with a list of voucher ids connected to that batch.
    """

    id: int
    created: str
    batchid: str
    period: Optional[int]
    interface: str
    client: str
    vouchertype: Optional[str]
    batch_validated_ok_date: Optional[str]
    batch_rejected_code: Optional[str]
    sent_date: Optional[str]
    http_response_content: Optional[str]
    http_response_code: Optional[int]
    orderno: Optional[int]
    polling_statuscode: Optional[str]
    polling_statuscode_date: Optional[str]
    getresult_date: Optional[str]
    getresult_statuscode: Optional[str]
    getresult_logg: Optional[str]
    getresult_report: Optional[str]
    batch_progress: Optional[str]
    vouchers: Optional[List[int]]


class ErrorTransaction(BaseModel):
    id: int
    transaction_id: int
    validation_date: datetime.datetime
    validation_error: str
    validation_error_type_code: str


class ErrorVoucher(BaseModel):
    id: int
    voucher_id: int
    validation_date: datetime.datetime
    validation_error: str
    validation_error_type_code: str


class ErrorBatch(BaseModel):
    id: int
    batch_id: int
    date: datetime.datetime
    error: str
    error_type_code: str
    error_level: str
    error_source: str


class BatchErrors(BaseModel):
    """Model for the /batch_error/<id> endpoint"""

    batch_errors: List[ErrorBatch]
    voucher_errors: List[ErrorVoucher]
    transaction_errors: List[ErrorTransaction]


class CompleteTransaction(BaseModel):
    id: int
    voucher_id: int
    created: datetime.datetime
    account: str
    amount: float
    transdate: Optional[datetime.datetime]
    curamount: float
    currency: str
    description: str
    dim1: Optional[str]
    dim2: Optional[str]
    dim3: Optional[str]
    dim4: Optional[str]
    dim5: Optional[str]
    dim6: Optional[str]
    dim7: Optional[str]
    sequenceno: int
    taxcode: Optional[str]
    transtype: str
    extinvref: Optional[str]


class CompleteVoucher(BaseModel):
    id: int
    batch_id: int
    created: datetime.datetime
    voucherdate: Optional[datetime.datetime]
    exref: str
    voucherno: int
    voucherno_ubw: Optional[int]
    voucherno_ubw_wflow: Optional[int]
    vouchertype: Optional[str]
    transactions: List[CompleteTransaction]


class CompleteBatch(BaseModel):
    """Model for the /batch_complete/<id> endpoint"""

    created: datetime.datetime
    batchid: str
    period: Optional[int]
    interface: str
    client: str
    vouchertype: Optional[str]
    batch_validated_ok_date: Optional[datetime.datetime]
    batch_rejected_code: Optional[str]
    sent_date: Optional[datetime.datetime]
    http_response_content: Optional[str]
    http_response_code: Optional[int]
    orderno: Optional[int]
    polling_statuscode: Optional[str]
    polling_statuscode_date: Optional[datetime.datetime]
    getresult_date: Optional[datetime.datetime]
    getresult_statuscode: Optional[str]
    getresult_logg: Optional[str]
    getresult_report: Optional[str]
    batch_progress: str
    vouchers: List[CompleteVoucher]


class Parameter(BaseModel):
    client: str
    created: datetime.datetime
    interface: str
    parameter: str
    mandatory: str
    validation: str
    value: str
    status: Optional[str]
    valid_from: Optional[datetime.date]
    valid_to: Optional[datetime.date]
    description: Optional[str]


class Detail(BaseModel):
    id: Optional[int]  # Cannot be sent in
    order_id: Optional[int]  # Cannot be sent in
    account: str
    buyer_product_descr: Optional[str]
    buyer_product_code: str
    line_total: Optional[float]
    koststed: str
    prosjekt: Optional[str]
    t1: Optional[str]
    r00: Optional[str]
    delprosjekt: str
    anlegg: Optional[str]
    aktivitet: Optional[str]
    line_no: int
    product_specification: Optional[int]
    price: Optional[float]
    quantity: Optional[float]


class Order(BaseModel):
    id: Optional[int]  # Cannot be sent in
    abw_order_id: Optional[int]  # Cannot be sent in
    accountable: Optional[str]
    buyer_no: str
    currency: str
    ext_order_ref: Optional[str]
    order_date: Optional[datetime.date]
    order_type: str
    sales_man: str
    trans_type: str
    ext_order_id: Optional[str]
    order_no: Optional[int]
    deliv_date: Optional[datetime.date]
    text3: Optional[str]
    text4: Optional[str]
    header_text: Optional[str]
    footer_text: Optional[str]
    details: Optional[List[Detail]]
    ubw_final_number: Optional[str]


class AbwOrder(BaseModel):
    """
    Model representing an AbwOrder, with a list of orders (and each order has a list of details.)
    """

    id: Optional[int]  # Cannot be sent in
    progress: Optional[str]  # Cannot be sent in
    abworder_validated_ok_date: Optional[datetime.datetime]  # Cannot be sent in
    responsible: str
    interface: str
    client: str
    abworderid: str
    ordrenr_ubw: Optional[str]  # Not sent in, but returned by the API.
    orders: List[Order]


class ErrorDetail(BaseModel):
    id: int
    detail_id: int
    validation_date: datetime.datetime
    validation_error: str
    validation_error_type_code: str


class ErrorOrder(BaseModel):
    id: int
    order_id: int
    validation_date: datetime.datetime
    validation_error: str
    validation_error_type_code: str


class ErrorAbwOrder(BaseModel):
    id: int
    abworder_id: int
    date: datetime.datetime
    error: str
    error_type_code: str
    error_level: str
    error_source: str


class AbwOrderErrors(BaseModel):
    """Model for the /abw_order_errors/<id> endpoint"""

    abw_order_errors: List[ErrorAbwOrder]
    order_errors: List[ErrorOrder]
    detail_errors: List[ErrorDetail]