import asyncio
import logging
from asyncio import AbstractEventLoop
from dataclasses import field, dataclass
from typing import Any

import pyaudio

from aiovban import VBANSampleRate
from aiovban.asyncio.streams import VBANOutgoingStream, BufferedVBANOutgoingStream
from aiovban.packet import VBANPacket, BytesBody
from aiovban.packet.headers.audio import VBANAudioHeader, BitResolution, Codec
from .enums import VBANPyAudioFormatMapping
from .util import run_on_background_thread

logger = logging.getLogger(__package__)


@dataclass
class VBANAudioSender:
    stream: VBANOutgoingStream
    device_index: int = 0

    sample_rate: VBANSampleRate = VBANSampleRate.RATE_48000
    channels: int = 2
    format: BitResolution = BitResolution.INT16
    framebuffer_size: int = 128
    sample_buffer_size: int = 3

    pyaudio: Any = field(default_factory=pyaudio.PyAudio, repr=False)
    _stream: Any = field(init=False, repr=False)
    _loop: Any = field(default=None, init=False, repr=False)
    _running: bool = field(default=False, init=False, repr=False)
    _sent_packet_count: int = field(default=0, init=False, repr=False)

    def __post_init__(self):
        self._stream = self.setup_stream()

    @property
    def bytes_per_frame(self):
        return self.channels * self.format.byte_width

    def setup_stream(self):
        return self.pyaudio.open(
            format=VBANPyAudioFormatMapping(self.format).pyaudio_format,
            channels=self.channels,
            rate=self.sample_rate.rate,
            input=True,
            input_device_index=self.device_index,
            frames_per_buffer=self.framebuffer_size,
        )

    def split_bytes_into_chunks(self, data, chunk_size):
        """Splits bytes into chunks of a given size."""
        return [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)]

    def pack_audio_data(self, audio_data):
        samples_per_frame = len(audio_data) // self.bytes_per_frame
        packet = VBANPacket(
            header=VBANAudioHeader(
                streamname=self.stream.name,
                sample_rate=self.sample_rate,
                codec=Codec.PCM,
                channels=self.channels,
                bit_resolution=self.format,
                samples_per_frame=samples_per_frame,
            ),
            body=BytesBody(audio_data),
        )
        if isinstance(self.stream, BufferedVBANOutgoingStream):
            # Safe cross-thread async scheduling
            self.stream.send_packet_threadsafe(packet, loop=self._loop)
            self._sent_packet_count += 1
        elif self._loop:
            # Fallback for non-buffered streams
            asyncio.run_coroutine_threadsafe(self.stream.send_packet(packet), self._loop)
            self._sent_packet_count += 1

        if self._sent_packet_count % 100 == 0:
            if logger.isEnabledFor(logging.DEBUG):
                logger.debug(f"Sent {self._sent_packet_count} packets")

    def send_all_audio_data(self, audio_data):
        chunk_size = self.framebuffer_size * self.bytes_per_frame
        chunks = self.split_bytes_into_chunks(audio_data, chunk_size)
        for chunk in chunks:
            self.pack_audio_data(chunk)

    def read_stream(self, amount):
        return self._stream.read(amount, exception_on_overflow=False)

    @run_on_background_thread
    async def listen(self, origin_loop: AbstractEventLoop):
        self._loop = origin_loop
        self._running = True
        self._stream.start_stream()

        try:
            while self._running:
                audio_data = self.read_stream(
                    self.framebuffer_size * self.sample_buffer_size
                )
                self.send_all_audio_data(audio_data)
        except Exception as e:
            logger.error(f"Error in audio sender: {e}")
        finally:
            self.stop()

    def stop(self):
        self._running = False
        if self._stream.is_active():
            self._stream.stop_stream()
        self._stream.close()
