from __future__ import annotations

from mf_transport import (
    Request,
    Response,
    ConnectionPool,
    TransportError,
    send_with_retry,
    SENTINEL,
)
from mf_codec import Frame, Encoder, Decoder, CodecError, encode_batch
from mf_core import RetryPolicy, MAX_RETRIES


class ProcessorError(TransportError):
    stage: str = ""
    codec_error: CodecError = CodecError()

    def describe(self: "ProcessorError") -> str:
        base: str = TransportError.describe(self)
        return base + " stage=" + self.stage


class ProcessorConfig:
    max_batch_size: int = 32
    max_retries: int = MAX_RETRIES
    retry: RetryPolicy = RetryPolicy()
    pool: ConnectionPool = ConnectionPool()
    encoder: Encoder = Encoder()
    decoder: Decoder = Decoder()


class ProcessingResult:
    success: bool = False
    frames_encoded: int = 0
    payload_decoded: int = 0
    response: Response = Response()
    error: ProcessorError = ProcessorError()

    def is_ok(self: "ProcessingResult") -> bool:
        return self.success and self.response.is_success()

    def total_data(self: "ProcessingResult") -> int:
        return self.frames_encoded + self.payload_decoded

    def has_error(self: "ProcessingResult") -> bool:
        return not self.success


def process_single(config: ProcessorConfig, payload_size: int) -> ProcessingResult:
    result: ProcessingResult = ProcessingResult()
    req: Request = Request()
    req.retry = config.retry
    req.payload_size = payload_size
    frames: int = config.encoder.encode(payload_size)
    if frames == SENTINEL:
        result.error.stage = "encode"
        return result
    result.frames_encoded = frames
    resp: Response = send_with_retry(config.pool, req)
    result.response = resp
    if resp.is_success():
        decoded: int = config.decoder.decode(frames)
        result.payload_decoded = decoded
        result.success = True
    else:
        result.error.stage = "transport"
        result.error.transport_code = resp.status
    return result


def process_batch(config: ProcessorConfig, sizes: list) -> int:
    total_frames: int = encode_batch(config.encoder, sizes)
    success_count: int = 0
    idx: int = 0
    while idx < len(sizes):
        result: ProcessingResult = process_single(config, sizes[idx])
        if result.is_ok():
            success_count = success_count + 1
        idx = idx + 1
    return success_count


class Processor:
    config: ProcessorConfig = ProcessorConfig()
    total_processed: int = 0
    total_failed: int = 0
    total_frames: int = 0
    max_retries: int = MAX_RETRIES

    def run(self: "Processor", payload_size: int) -> ProcessingResult:
        result: ProcessingResult = process_single(self.config, payload_size)
        if result.is_ok():
            self.total_processed = self.total_processed + 1
            self.total_frames = self.total_frames + result.frames_encoded
        else:
            self.total_failed = self.total_failed + 1
        return result

    def run_batch(self: "Processor", sizes: list) -> int:
        count: int = process_batch(self.config, sizes)
        self.total_processed = self.total_processed + count
        self.total_failed = self.total_failed + len(sizes) - count
        return count

    def success_rate(self: "Processor") -> int:
        total: int = self.total_processed + self.total_failed
        if total == 0:
            return 100
        return self.total_processed * 100 // total

    def reset(self: "Processor") -> None:
        self.total_processed = 0
        self.total_failed = 0
        self.total_frames = 0


GLOBAL_PROCESSOR: Processor = Processor()
DEFAULT_SENTINEL: int = SENTINEL
MAX_RETRY_LIMIT: int = MAX_RETRIES
