# -*- coding: utf-8 -*-
"""
API de alto nivel - Reproductor simple.

Proporciona una interfaz Pythonica para las operaciones más comunes
de reproducción de audio con BASS.
"""

import ctypes
import sys
from pathlib import Path
from typing import Optional

from ..dlls.bass import bass
from ..constantes.bass import (
	BASS_STREAM_AUTOFREE,
	BASS_STREAM_PRESCAN,
	BASS_UNICODE,
	BASS_POS_BYTE,
	BASS_ACTIVE_PLAYING,
	BASS_ACTIVE_PAUSED,
	BASS_ACTIVE_STOPPED,
	BASS_ACTIVE_STALLED,
	BASS_ATTRIB_VOL,
	BASS_ATTRIB_PAN,
	BASS_ATTRIB_FREQ,
)
from ..excepciones import ErrorBassNativo
from ..tipos import version_a_texto


def _verificar_error(resultado: int, nombre_funcion: str) -> None:
	"""Verifica si una llamada BASS retornó error y lanza excepción."""
	if resultado == 0:
		codigo = bass.BASS_ErrorGetCode()
		if codigo != 0:
			raise ErrorBassNativo(codigo, nombre_funcion)


def inicializar(
	dispositivo: int = -1,
	frecuencia: int = 44100,
	flags: int = 0,
) -> None:
	"""
	Inicializa BASS con el dispositivo de salida indicado.

	Parámetros:
		dispositivo: -1 para dispositivo por defecto, 0 para sin sonido.
		frecuencia: Frecuencia de muestreo (por defecto 44100 Hz).
		flags: Flags de inicialización (BASS_DEVICE_xxx).

	Lanza:
		ErrorBassNativo: Si la inicialización falla.
	"""
	resultado = bass.BASS_Init(dispositivo, frecuencia, flags, 0, None)
	_verificar_error(resultado, "BASS_Init")


def liberar() -> None:
	"""
	Libera los recursos del dispositivo BASS actual.

	Debe llamarse al terminar de usar BASS.
	"""
	bass.BASS_Free()


def obtener_version() -> str:
	"""
	Obtiene la versión de BASS cargada como texto legible.

	Retorna:
		String con la versión (ej: '2.4.18.3').
	"""
	return version_a_texto(bass.BASS_GetVersion())


def obtener_version_raw() -> int:
	"""
	Obtiene el valor DWORD crudo de versión de BASS.

	Retorna:
		Entero con el valor de versión codificado.
	"""
	return bass.BASS_GetVersion()


class Reproductor:
	"""
	Reproductor de audio de alto nivel.

	Permite reproducir archivos y URLs de forma sencilla,
	controlando volumen, posición, pausa, etc.

	Ejemplo:
		from basskit.alto_nivel.reproductor import Reproductor, inicializar, liberar
		inicializar()
		rep = Reproductor()
		rep.abrir_archivo("cancion.mp3")
		rep.reproducir()
		# ... esperar ...
		rep.detener()
		liberar()
	"""

	def __init__(self) -> None:
		"""Inicializa el reproductor sin canal activo."""
		self._handle: int = 0

	@property
	def activo(self) -> bool:
		"""Indica si hay un canal cargado."""
		return self._handle != 0

	def abrir_archivo(
		self,
		ruta: str | Path,
		flags: int = 0,
	) -> None:
		"""
		Abre un archivo de audio para reproducción.

		Parámetros:
			ruta: Ruta al archivo de audio.
			flags: Flags adicionales de stream (BASS_STREAM_xxx).

		Lanza:
			ErrorBassNativo: Si no se puede abrir el archivo.
			FileNotFoundError: Si el archivo no existe.
		"""
		ruta = Path(ruta)
		if not ruta.exists():
			raise FileNotFoundError(f"Archivo no encontrado: {ruta}")

		self.cerrar()

		# Usar BASS_UNICODE para nombres con caracteres especiales
		ruta_bytes = str(ruta).encode("utf-16-le") + b"\x00\x00"
		flags_final = flags | BASS_UNICODE | BASS_STREAM_PRESCAN

		handle = bass.BASS_StreamCreateFile(
			0,								# mem = False
			ruta_bytes,						# file
			0,								# offset
			0,								# length (todo el archivo)
			flags_final,
		)

		if handle == 0:
			codigo = bass.BASS_ErrorGetCode()
			raise ErrorBassNativo(codigo, "BASS_StreamCreateFile")

		self._handle = handle

	def abrir_url(
		self,
		url: str,
		flags: int = 0,
	) -> None:
		"""
		Abre un stream desde una URL.

		Parámetros:
			url: URL del stream de audio.
			flags: Flags adicionales.

		Lanza:
			ErrorBassNativo: Si no se puede conectar o abrir el stream.
		"""
		self.cerrar()

		url_bytes = url.encode("utf-8")
		handle = bass.BASS_StreamCreateURL(url_bytes, 0, flags, None, None)

		if handle == 0:
			codigo = bass.BASS_ErrorGetCode()
			raise ErrorBassNativo(codigo, "BASS_StreamCreateURL")

		self._handle = handle

	def reproducir(self, reiniciar: bool = False) -> None:
		"""
		Inicia o reanuda la reproducción.

		Parámetros:
			reiniciar: Si True, reinicia desde el principio.
		"""
		if not self.activo:
			return
		resultado = bass.BASS_ChannelPlay(self._handle, 1 if reiniciar else 0)
		_verificar_error(resultado, "BASS_ChannelPlay")

	def pausar(self) -> None:
		"""Pausa la reproducción."""
		if not self.activo:
			return
		bass.BASS_ChannelPause(self._handle)

	def detener(self) -> None:
		"""Detiene la reproducción."""
		if not self.activo:
			return
		bass.BASS_ChannelStop(self._handle)

	def cerrar(self) -> None:
		"""Cierra el canal actual y libera recursos."""
		if self._handle != 0:
			bass.BASS_StreamFree(self._handle)
			self._handle = 0

	@property
	def estado(self) -> str:
		"""
		Estado actual de reproducción.

		Retorna:
			'reproduciendo', 'pausado', 'detenido', 'esperando' o 'inactivo'.
		"""
		if not self.activo:
			return "inactivo"
		estado = bass.BASS_ChannelIsActive(self._handle)
		estados = {
			BASS_ACTIVE_PLAYING: "reproduciendo",
			BASS_ACTIVE_PAUSED: "pausado",
			BASS_ACTIVE_STOPPED: "detenido",
			BASS_ACTIVE_STALLED: "esperando",
		}
		return estados.get(estado, "desconocido")

	@property
	def volumen(self) -> float:
		"""Volumen del canal (0.0 a 1.0)."""
		if not self.activo:
			return 0.0
		val = ctypes.c_float(0.0)
		bass.BASS_ChannelGetAttribute(self._handle, BASS_ATTRIB_VOL, ctypes.byref(val))
		return val.value

	@volumen.setter
	def volumen(self, valor: float) -> None:
		"""Establece el volumen (0.0 a 1.0)."""
		if self.activo:
			bass.BASS_ChannelSetAttribute(self._handle, BASS_ATTRIB_VOL, valor)

	@property
	def posicion_segundos(self) -> float:
		"""Posición actual en segundos."""
		if not self.activo:
			return 0.0
		pos_bytes = bass.BASS_ChannelGetPosition(self._handle, BASS_POS_BYTE)
		return bass.BASS_ChannelBytes2Seconds(self._handle, pos_bytes)

	@posicion_segundos.setter
	def posicion_segundos(self, segundos: float) -> None:
		"""Establece la posición en segundos."""
		if self.activo:
			pos_bytes = bass.BASS_ChannelSeconds2Bytes(self._handle, segundos)
			bass.BASS_ChannelSetPosition(self._handle, pos_bytes, BASS_POS_BYTE)

	@property
	def duracion_segundos(self) -> float:
		"""Duración total en segundos."""
		if not self.activo:
			return 0.0
		len_bytes = bass.BASS_ChannelGetLength(self._handle, BASS_POS_BYTE)
		if len_bytes < 0:
			return 0.0
		return bass.BASS_ChannelBytes2Seconds(self._handle, len_bytes)

	def __del__(self) -> None:
		"""Libera recursos al destruir el objeto."""
		self.cerrar()

	def __repr__(self) -> str:
		return f"<Reproductor handle={self._handle} estado='{self.estado}'>"
