import re
import subprocess
import pathlib
import dataclasses
import logging

from typing import (
	ClassVar,
	Optional,
	Any,
)

from .models import (
	package_desc_t,
)

logger = logging.getLogger(__name__)


class pacman_t:
	class constants_t:
		default_db_path: ClassVar[pathlib.Path] = pathlib.Path('/var/lib/pacman')
		default_cache_dir: ClassVar[pathlib.Path] = pathlib.Path('/var/cache/pacman/pkg')
		field_re: ClassVar[re.Pattern[str]] = re.compile(r'^([A-Za-z ]+?)\s*:\s*(.*)$')

	@dataclasses.dataclass
	class query_entry_t:
		name: str
		version: str
		description: str = ''
		architecture: str = ''
		url: str = ''
		depends_on: list[str] = dataclasses.field(default_factory=lambda: list[str]())
		provides: list[str] = dataclasses.field(default_factory=lambda: list[str]())
		conflicts_with: list[str] = dataclasses.field(default_factory=lambda: list[str]())
		replaces: list[str] = dataclasses.field(default_factory=lambda: list[str]())
		install_size: str = ''
		packager: str = ''
		groups: list[str] = dataclasses.field(default_factory=lambda: list[str]())

	class list_installed_t:
		@dataclasses.dataclass
		class res_t:
			packages: list['pacman_t.query_entry_t'] = dataclasses.field(default_factory=lambda: list[pacman_t.query_entry_t]())

	@staticmethod
	def parse_info_block(block: str) -> 'pacman_t.query_entry_t':
		fields: dict[str, list[str]] = {}
		current_key: Optional[str] = None

		for line in block.split('\n'):
			m = pacman_t.constants_t.field_re.match(line)
			if m:
				current_key = m.group(1).strip()
				value = m.group(2).strip()
				assert isinstance(current_key, str)
				if current_key not in fields:
					fields[current_key] = []
				if value and value != 'None':
					fields[current_key].append(value)
			elif current_key and line.startswith(' '):
				value = line.strip()
				if value and value != 'None':
					fields[current_key].append(value)

		name = fields.get('Name', [''])[0]
		version = fields.get('Version', [''])[0]

		if not name or not version:
			raise ValueError('missing Name or Version in block')

		return pacman_t.query_entry_t(
			name=name,
			version=version,
			description=fields.get('Description', [''])[0] if fields.get('Description') else '',
			architecture=fields.get('Architecture', [''])[0] if fields.get('Architecture') else '',
			url=fields.get('URL', [''])[0] if fields.get('URL') else '',
			depends_on=fields.get('Depends On', []),
			provides=fields.get('Provides', []),
			conflicts_with=fields.get('Conflicts With', []),
			replaces=fields.get('Replaces', []),
			install_size=fields.get('Installed Size', [''])[0] if fields.get('Installed Size') else '',
			packager=fields.get('Packager', [''])[0] if fields.get('Packager') else '',
			groups=fields.get('Groups', []),
		)

	@staticmethod
	def list_installed(
		db_path: Optional[pathlib.Path] = None,
	) -> 'pacman_t.list_installed_t.res_t':
		cmd: list[str] = ['pacman', '-Qi']

		if db_path is not None:
			cmd.extend(['--dbpath', str(db_path)])

		output = subprocess.check_output(
			cmd,
			stderr=subprocess.DEVNULL,
		).decode('utf-8')

		blocks = output.split('\n\n')
		result = pacman_t.list_installed_t.res_t()

		for block in blocks:
			block = block.strip()
			if not block:
				continue

			try:
				entry = pacman_t.parse_info_block(block)
				result.packages.append(entry)
			except ValueError:
				logger.warning(
					dict(
						msg='failed to parse pacman info block',
					)
				)

		return result

	@staticmethod
	def list_installed_simple(
		db_path: Optional[pathlib.Path] = None,
	) -> list[tuple[str, str]]:
		cmd: list[str] = ['pacman', '-Q']

		if db_path is not None:
			cmd.extend(['--dbpath', str(db_path)])

		output = subprocess.check_output(
			cmd,
			stderr=subprocess.DEVNULL,
		).decode('utf-8')

		result: list[tuple[str, str]] = []

		for line in output.strip().split('\n'):
			parts = line.strip().split(None, 1)
			if len(parts) == 2:
				result.append((parts[0], parts[1]))

		return result

	@staticmethod
	def sync_db(
		mirror_url: str,
		db_path: pathlib.Path,
		repos: Optional[list[str]] = None,
	) -> None:
		if repos is None:
			repos = ['core', 'extra', 'multilib']

		cmd: list[str] = [
			'pacman',
			'-Sy',
			'--dbpath',
			str(db_path),
		]

		subprocess.check_call(cmd)

	@staticmethod
	def download_db(
		url: str,
		output_path: pathlib.Path,
	) -> None:
		import urllib.request

		logger.info(
			dict(
				url=url,
				output_path=str(output_path),
				msg='downloading db',
			)
		)

		output_path.parent.mkdir(parents=True, exist_ok=True)

		urllib.request.urlretrieve(
			url,
			str(output_path),
		)
