from __future__ import annotations

from mf_processor import Processor, ProcessorConfig, ProcessingResult, ProcessorError
from mf_transport import ConnectionPool, Request, Response, send_with_retry
from mf_codec import CodecError
from mf_core import MAX_RETRIES, SENTINEL, ConnectionConfig, RetryPolicy
from mf_core import CHUNK_SIZE as BUFFER_SIZE

OrchestratorError = ProcessorError


class OrchestratorStats:
    total_requests: int = 0
    successful_requests: int = 0
    failed_requests: int = 0
    total_frames: int = 0
    sentinel_hits: int = 0
    codec_errors: int = 0

    def success_rate(self: "OrchestratorStats") -> int:
        if self.total_requests == 0:
            return 100
        return self.successful_requests * 100 // self.total_requests

    def avg_frames(self: "OrchestratorStats") -> int:
        if self.successful_requests == 0:
            return 0
        return self.total_frames // self.successful_requests

    def is_healthy(self: "OrchestratorStats") -> bool:
        rate: int = self.success_rate()
        return rate >= 95

    def reset(self: "OrchestratorStats") -> None:
        self.total_requests = 0
        self.successful_requests = 0
        self.failed_requests = 0
        self.total_frames = 0
        self.sentinel_hits = 0
        self.codec_errors = 0

class Orchestrator:
    processor: Processor = Processor()
    pool: ConnectionPool = ConnectionPool()
    stats: OrchestratorStats = OrchestratorStats()
    config: ProcessorConfig = ProcessorConfig()
    max_retries: int = MAX_RETRIES
    buffer_size: int = BUFFER_SIZE

    def initialize(self: "Orchestrator", conn_config: ConnectionConfig) -> None:
        self.pool.config = conn_config
        self.config.pool = self.pool
        self.processor.config = self.config

    def handle(self: "Orchestrator", payload_size: int) -> bool:
        self.stats.total_requests = self.stats.total_requests + 1
        result: ProcessingResult = self.processor.run(payload_size)
        if result.is_ok():
            self.stats.successful_requests = self.stats.successful_requests + 1
            self.stats.total_frames = self.stats.total_frames + result.frames_encoded
            return True
        self.stats.failed_requests = self.stats.failed_requests + 1
        return False

    def handle_batch(self: "Orchestrator", sizes: list) -> int:
        success: int = self.processor.run_batch(sizes)
        self.stats.total_requests = self.stats.total_requests + len(sizes)
        self.stats.successful_requests = self.stats.successful_requests + success
        self.stats.failed_requests = self.stats.failed_requests + len(sizes) - success
        return success

    def get_health(self: "Orchestrator") -> int:
        return self.stats.success_rate()

    def direct_send(self: "Orchestrator", payload_size: int) -> Response:
        req: Request = Request()
        req.payload_size = payload_size
        retry: RetryPolicy = RetryPolicy()
        retry.max_attempts = self.max_retries
        req.retry = retry
        resp: Response = send_with_retry(self.pool, req)
        if resp.status == SENTINEL:
            self.stats.sentinel_hits = self.stats.sentinel_hits + 1
        return resp

    def record_codec_error(self: "Orchestrator", err: CodecError) -> None:
        if err.is_fatal():
            self.stats.codec_errors = self.stats.codec_errors + 1


def build_orchestrator(host: str, port: int) -> Orchestrator:
    orch: Orchestrator = Orchestrator()
    conn: ConnectionConfig = ConnectionConfig()
    conn.host = host
    conn.port = port
    conn.max_connections = MAX_RETRIES * 10
    policy: RetryPolicy = RetryPolicy()
    policy.max_attempts = MAX_RETRIES
    orch.config.retry = policy
    orch.initialize(conn)
    return orch

GLOBAL_ORCHESTRATOR: Orchestrator = Orchestrator()
GLOBAL_POOL: ConnectionPool = ConnectionPool()

SENTINEL_STATUS: int = SENTINEL
MAX_RETRY_COUNT: int = MAX_RETRIES
BUFFER: int = BUFFER_SIZE
ORCH_ERROR: OrchestratorError = OrchestratorError()
