import io
import hashlib
import pathlib
import tempfile
import logging

from typing import (
	Optional,
	Any,
)

from .models import (
	compile_options_t,
	compile_entry_t,
	compile_result_t,
	mirror_config_t,
	repo_index_t,
)

from .db import db_parser_t
from .pacman import pacman_t
from .resolver import resolver_t

logger = logging.getLogger(__name__)


class compile_t:
	@staticmethod
	def build_mirror_config(options: compile_options_t) -> mirror_config_t:
		if options.archive_date is not None:
			return mirror_config_t.from_archive_date(
				date=options.archive_date,
				repos=options.repos,
				arch=options.arch,
			)
		elif options.index_url is not None:
			return mirror_config_t.from_mirror_url(
				mirror_url=options.index_url,
				repos=options.repos,
				arch=options.arch,
			)
		else:
			return mirror_config_t.from_mirror_url(
				mirror_url='https://archive.archlinux.org/repos/last',
				repos=options.repos,
				arch=options.arch,
			)

	@staticmethod
	def fetch_indices(
		mirror: mirror_config_t,
		cache_dir: Optional[pathlib.Path] = None,
		no_cache: bool = False,
		offline: bool = False,
	) -> list[repo_index_t]:
		indices: list[repo_index_t] = []

		for repo in mirror.repos:
			db_url = '%s/%s.db' % (repo.url, repo.name)

			if cache_dir is not None and not no_cache:
				cached_path = cache_dir / ('%s.db' % repo.name)

				if cached_path.exists():
					logger.info(
						dict(
							repo=repo.name,
							msg='using cached db',
							path=str(cached_path),
						)
					)
					index = db_parser_t.parse_db_path(cached_path, repo_name=repo.name)
					indices.append(index)
					continue

				if offline:
					raise FileNotFoundError('offline mode: cached db not found for %s at %s' % (repo.name, str(cached_path)))

				pacman_t.download_db(db_url, cached_path)
				index = db_parser_t.parse_db_path(cached_path, repo_name=repo.name)
				indices.append(index)
			else:
				if offline:
					raise FileNotFoundError('offline mode requires --cache-dir with pre-fetched db files')

				with tempfile.NamedTemporaryFile(suffix='.db') as tmp:
					pacman_t.download_db(db_url, pathlib.Path(tmp.name))
					index = db_parser_t.parse_db_path(pathlib.Path(tmp.name), repo_name=repo.name)
					indices.append(index)

		return indices

	@staticmethod
	def compile(
		options: compile_options_t,
	) -> compile_result_t.res_t:
		mirror = compile_t.build_mirror_config(options)

		cache_dir: Optional[pathlib.Path] = None
		if options.cache_dir is not None:
			cache_dir = pathlib.Path(options.cache_dir)
			cache_dir.mkdir(parents=True, exist_ok=True)

		indices = compile_t.fetch_indices(
			mirror=mirror,
			cache_dir=cache_dir,
			no_cache=options.no_cache,
			offline=options.offline,
		)

		resolved = resolver_t.resolve(
			packages=options.packages,
			indices=indices,
		)

		result = compile_result_t.res_t()

		for pkg_name in resolved.resolution_order:
			pkg = resolved.resolved[pkg_name]

			repo_name = ''
			for idx in indices:
				if pkg_name in idx.packages:
					repo_name = idx.name
					break

			repo_url = ''
			for repo_cfg in mirror.repos:
				if repo_cfg.name == repo_name:
					repo_url = repo_cfg.url
					break

			entry = compile_entry_t(
				name=pkg.name,
				version=pkg.version,
				filename=pkg.filename,
				repo=repo_name,
				url='%s/%s' % (repo_url, pkg.filename) if repo_url and pkg.filename else '',
				sha256=pkg.sha256sum if options.generate_hashes else '',
				depends=pkg.depends,
			)

			result.entries.append(entry)

		result.txt = result.to_txt()

		return result
