# -*- coding: utf-8 -*-
"""
API de alto nivel - Lector de tags de audio.

Extrae metadatos de archivos de audio usando Tags.dll y BASS.
"""

import ctypes
from pathlib import Path
from typing import Optional

from ..dlls.bass import bass
from ..constantes.bass import (
	BASS_TAG_ID3, BASS_TAG_ID3V2, BASS_TAG_OGG,
	BASS_TAG_APE, BASS_TAG_WMA, BASS_TAG_MP4,
	BASS_TAG_MUSIC_NAME, BASS_TAG_MUSIC_AUTH,
	BASS_TAG_MUSIC_MESSAGE, BASS_UNICODE,
	BASS_STREAM_DECODE, BASS_STREAM_PRESCAN,
)
from ..estructuras.bass import TAG_ID3


def _leer_tags_ogg(handle: int) -> dict[str, str]:
	"""Lee tags OGG/Vorbis comment de un canal."""
	resultado = {}
	ptr = bass.BASS_ChannelGetTags(handle, BASS_TAG_OGG)
	if not ptr:
		return resultado
	direccion = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_char))
	offset = 0
	while True:
		cadena = ctypes.string_at(ctypes.addressof(direccion.contents) + offset)
		if not cadena:
			break
		texto = cadena.decode("utf-8", errors="replace")
		if "=" in texto:
			clave, valor = texto.split("=", 1)
			resultado[clave.upper()] = valor
		offset += len(cadena) + 1
	return resultado


def _leer_tag_id3v1(handle: int) -> dict[str, str]:
	"""Lee tags ID3v1 de un canal."""
	ptr = bass.BASS_ChannelGetTags(handle, BASS_TAG_ID3)
	if not ptr:
		return {}
	tag = ctypes.cast(ptr, ctypes.POINTER(TAG_ID3)).contents
	resultado = {}
	for campo in ["title", "artist", "album", "year"]:
		valor = getattr(tag, campo, b"")
		if valor:
			texto = valor.decode("latin-1", errors="replace").strip("\x00").strip()
			if texto:
				resultado[campo.upper()] = texto
	if tag.genre < 255:
		resultado["GENRE_ID"] = str(tag.genre)
	return resultado


class LectorTags:
	"""
	Lector de metadatos de archivos de audio.

	Lee tags ID3v1, ID3v2, OGG comments, APE y otros formatos.

	Ejemplo:
		from basskit.alto_nivel.lector_tags import LectorTags
		lector = LectorTags()
		tags = lector.leer("cancion.mp3")
		print(tags.get("TITLE", "Sin titulo"))
		print(tags.get("ARTIST", "Desconocido"))
	"""

	def leer(self, ruta: str | Path) -> dict[str, str]:
		"""
		Lee los tags de un archivo de audio.

		Parametros:
			ruta: Ruta al archivo de audio.

		Retorna:
			Diccionario con los tags encontrados (claves en MAYUSCULAS).
		"""
		ruta = Path(ruta)
		if not ruta.exists():
			raise FileNotFoundError(f"Archivo no encontrado: {ruta}")

		ruta_bytes = str(ruta).encode("utf-16-le") + b"\x00\x00"
		flags = BASS_STREAM_DECODE | BASS_UNICODE

		handle = bass.BASS_StreamCreateFile(0, ruta_bytes, 0, 0, flags)
		if handle == 0:
			return {}

		try:
			tags = self._extraer_todos(handle)
			return tags
		finally:
			bass.BASS_StreamFree(handle)

	def leer_de_canal(self, handle: int) -> dict[str, str]:
		"""
		Lee tags de un canal BASS ya abierto.

		Parametros:
			handle: Handle de canal BASS activo.

		Retorna:
			Diccionario con los tags encontrados.
		"""
		return self._extraer_todos(handle)

	def _extraer_todos(self, handle: int) -> dict[str, str]:
		"""Intenta extraer tags de todos los formatos posibles."""
		tags: dict[str, str] = {}

		# OGG/Vorbis comments (FLAC, OGG, OPUS)
		ogg = _leer_tags_ogg(handle)
		if ogg:
			tags.update(ogg)

		# ID3v1
		id3v1 = _leer_tag_id3v1(handle)
		if id3v1:
			for k, v in id3v1.items():
				if k not in tags:
					tags[k] = v

		# MOD music tags
		ptr = bass.BASS_ChannelGetTags(handle, BASS_TAG_MUSIC_NAME)
		if ptr:
			nombre = ctypes.string_at(ptr).decode("utf-8", errors="replace")
			if nombre:
				tags.setdefault("TITLE", nombre)

		ptr = bass.BASS_ChannelGetTags(handle, BASS_TAG_MUSIC_AUTH)
		if ptr:
			autor = ctypes.string_at(ptr).decode("utf-8", errors="replace")
			if autor:
				tags.setdefault("ARTIST", autor)

		return tags

	def leer_con_tags_dll(self, handle: int, formato: str = "%IFV1(%ITRM(%TITL),no title)") -> str:
		"""
		Lee tags usando la libreria tags.dll (formato flexible).

		Parametros:
			handle: Handle de canal BASS.
			formato: String de formato de tags.dll.

		Retorna:
			String formateado con los tags.
		"""
		try:
			from ..dlls.addons import tags as tags_dll
			resultado = tags_dll.TAGS_Read(handle, formato.encode("utf-8"))
			if resultado:
				return resultado.decode("utf-8", errors="replace")
		except (OSError, AttributeError):
			pass
		return ""
