import io
import re
import tarfile
import logging
import pathlib

from typing import (
	ClassVar,
	Optional,
	Any,
	BinaryIO,
)

from .models import (
	package_desc_t,
	repo_index_t,
)

logger = logging.getLogger(__name__)


class db_parser_t:
	class constants_t:
		field_re: ClassVar[re.Pattern[str]] = re.compile(r'^%([A-Z0-9]+)%$')

		list_fields: ClassVar[set[str]] = {
			'LICENSE',
			'DEPENDS',
			'OPTDEPENDS',
			'MAKEDEPENDS',
			'CHECKDEPENDS',
			'PROVIDES',
			'CONFLICTS',
			'REPLACES',
			'GROUPS',
		}

		field_map: ClassVar[dict[str, str]] = {
			'FILENAME': 'filename',
			'NAME': 'name',
			'VERSION': 'version',
			'DESC': 'desc',
			'CSIZE': 'csize',
			'ISIZE': 'isize',
			'MD5SUM': 'md5sum',
			'SHA256SUM': 'sha256sum',
			'URL': 'url',
			'ARCH': 'arch',
			'BUILDDATE': 'builddate',
			'PACKAGER': 'packager',
			'LICENSE': 'license',
			'DEPENDS': 'depends',
			'OPTDEPENDS': 'optdepends',
			'MAKEDEPENDS': 'makedepends',
			'CHECKDEPENDS': 'checkdepends',
			'PROVIDES': 'provides',
			'CONFLICTS': 'conflicts',
			'REPLACES': 'replaces',
			'GROUPS': 'groups',
			'BASE': 'base',
		}

		int_fields: ClassVar[set[str]] = {
			'CSIZE',
			'ISIZE',
			'BUILDDATE',
		}

	@staticmethod
	def parse_desc(content: str) -> package_desc_t:
		fields: dict[str, Any] = {}
		lines = content.split('\n')
		i = 0

		while i < len(lines):
			line = lines[i].strip()

			if line == '':
				i += 1
				continue

			m = db_parser_t.constants_t.field_re.match(line)
			if not m:
				i += 1
				continue

			field_name = m.group(1)
			i += 1

			values: list[str] = []
			while i < len(lines) and lines[i].strip() != '':
				values.append(lines[i].strip())
				i += 1

			attr_name = db_parser_t.constants_t.field_map.get(field_name)
			if attr_name is None:
				continue

			if field_name in db_parser_t.constants_t.list_fields:
				fields[attr_name] = values
			elif field_name in db_parser_t.constants_t.int_fields:
				fields[attr_name] = int(values[0]) if len(values) > 0 else 0
			else:
				fields[attr_name] = values[0] if len(values) > 0 else ''

		if 'name' not in fields or 'version' not in fields:
			raise ValueError('desc missing NAME or VERSION')

		return package_desc_t(**fields)

	@staticmethod
	def parse_db(
		f: BinaryIO,
		repo_name: str = '',
	) -> repo_index_t:
		index = repo_index_t(name=repo_name)

		with tarfile.open(fileobj=f, mode='r:*') as tar:
			desc_members: list[tarfile.TarInfo] = []

			for member in tar.getmembers():
				if member.name.endswith('/desc') and member.isfile():
					desc_members.append(member)

			for member in desc_members:
				extracted = tar.extractfile(member)
				if extracted is None:
					continue

				content = extracted.read().decode('utf-8')
				extracted.close()

				try:
					pkg = db_parser_t.parse_desc(content)
					index.packages[pkg.name] = pkg
				except ValueError:
					logger.warning(
						dict(
							member=member.name,
							msg='failed to parse desc',
						)
					)

		index.build_provides_index()

		return index

	@staticmethod
	def parse_db_path(
		path: pathlib.Path,
		repo_name: Optional[str] = None,
	) -> repo_index_t:
		if repo_name is None:
			repo_name = path.stem.split('.')[0]

		with io.open(path, 'rb') as f:
			return db_parser_t.parse_db(f, repo_name=repo_name)
