#!/usr/bin/python3
# -*- coding: utf-8 -*-

from __future__ import annotations

import subprocess
from pathlib import Path

import urllib3  # pylint: disable=import-error

from sbo_create.handlers.files import FileHandler
from sbo_create.models import PROG, UNSUPPORTED, AppContext, colorize, confirm


class DownloadHandler:
    """Handle source file downloads and checksum verification."""

    def __init__(self, ctx: AppContext, files: FileHandler) -> None:
        self.ctx = ctx
        self.files = files

    def sources_download(self, check_urls: bool = False) -> None:
        """Download the sources listed in the .info file.

        Reads download URLs and expected MD5 checksums from the .info file,
        skips files that are already present, and prompts before downloading.
        Each downloaded file is verified against its expected checksum.

        Args:
            check_urls: If True, perform HTTP HEAD requests to verify URL
                accessibility instead of downloading the files.
        """
        self.files.read_info_file()

        if not self.ctx.info.download_x86 and not self.ctx.info.download_x86_64:
            print(colorize(f"{PROG}: No DOWNLOAD found in '{self.ctx.prg_name}.info'.", 'red'))
            raise SystemExit(1)

        links: list[str] = []
        md5_map: dict[str, str] = {}
        for downloads, md5sums in [
            (self.ctx.info.download_x86, self.ctx.info.md5sum_x86),
            (self.ctx.info.download_x86_64, self.ctx.info.md5sum_x86_64),
        ]:
            if downloads and downloads not in UNSUPPORTED:
                urls = downloads.split()
                sums = md5sums.split() if md5sums else []
                links.extend(urls)
                for url, md5 in zip(urls, sums):
                    md5_map[url.split('/')[-1]] = md5

        if check_urls:
            if not links:
                print("No URLs to check.")
                raise SystemExit(0)
            self._check_url_accessibility(links)
            raise SystemExit(0)

        new_links: list[str] = [
            lnk for lnk in links
            if not Path(self.ctx.current_folder, lnk.split('/')[-1]).is_file()
        ]

        if not new_links:
            print("All sources already downloaded.")
            raise SystemExit(0)

        print(f"Sources to download ({len(new_links)}):\n")
        for lnk in new_links:
            print(colorize(f"  {lnk.split('/')[-1]}", "green"))

        if not confirm("Proceed?"):
            raise SystemExit(0)

        md5s: list[str] = self.run_downloads(new_links, md5_map)
        failed: bool = any(m == '' for m in md5s)
        if not failed:
            print(colorize(
                f"All {len(new_links)} sources downloaded. All checksums verified.", 'green'))
        raise SystemExit(1 if failed else 0)

    def _check_url_accessibility(self, links: list[str]) -> None:
        """Perform HTTP HEAD requests to check whether download URLs are reachable.

        Prints a status line for each URL showing OK (200), a redirect warning,
        a HEAD-not-supported warning (405), or FAILED for errors and non-200
        responses. Connection errors are caught and reported as FAILED.

        Args:
            links: List of download URLs to check.
        """
        total: int = len(links)
        width: int = len(str(total))
        col_width: int = max(len(lnk.split('/')[-1]) for lnk in links) + 2
        http = urllib3.PoolManager()
        print()
        for idx, url in enumerate(links, 1):
            filename: str = url.split('/')[-1]
            counter: str = f"[{idx:{width}}/{total}]"
            dots: str = '.' * (col_width - len(filename))
            print(f"  {counter}  {filename} {dots}", end=' ', flush=True)
            try:
                resp = http.request('HEAD', url, timeout=10.0, redirect=True)
                if resp.status == 200:
                    print(colorize('OK', 'green'))
                elif resp.status == 405:
                    print(colorize('WARN', 'yellow') + ' (HEAD not supported)')
                elif 300 <= resp.status < 400:
                    print(colorize('REDIRECT', 'yellow') + f' ({resp.status})')
                else:
                    print(colorize('FAILED', 'red') + f' (HTTP {resp.status})')
            except Exception:  # pylint: disable=broad-except
                print(colorize('FAILED', 'red') + ' (connection error)')
        print()

    def run_downloads(self, new_links: list[str], md5_map: dict[str, str],
                      verify: bool = True) -> list[str]:
        """Download each URL and optionally verify its MD5 checksum.

        Displays a progress counter for each file. After a successful download,
        computes the file's MD5 and compares it against the expected value from
        md5_map. Files with no expected checksum are reported as OK regardless.

        Args:
            new_links: List of URLs to download.
            md5_map: Mapping of filename to expected MD5 checksum string.
            verify: If False, skip checksum comparison and report OK for any
                successfully downloaded file.

        Returns:
            List of actual MD5 hex strings, one per link. An empty string
            indicates a download failure or checksum mismatch.
        """
        total: int = len(new_links)
        width: int = len(str(total))
        col_width: int = max(len(lnk.split('/')[-1]) for lnk in new_links) + 2
        results: list[str] = []
        for idx, lnk in enumerate(new_links, 1):
            filename: str = lnk.split('/')[-1]
            counter: str = f"[{idx:{width}}/{total}]"
            dots: str = '.' * (col_width - len(filename))
            print(f"  {counter}  {filename} {dots}", end=' ', flush=True)
            if not self.download(lnk):
                print(colorize('FAILED', 'red'))
                results.append('')
                continue
            actual: str = self.files.source_check_sum(filename)
            if not actual:
                print(colorize('FAILED (file not found after download)', 'red'))
                results.append('')
                continue
            if not verify:
                print(colorize('OK', 'green'))
            else:
                expected: str = md5_map.get(filename, '')
                if not expected:
                    print(colorize('OK', 'green') + ' (no checksum)')
                elif actual == expected:
                    print(colorize('OK', 'green'))
                else:
                    print(colorize('md5 MISMATCH', 'red'))
                    actual = ''
            results.append(actual)
        print()
        return results

    def download(self, link: str) -> bool:
        """Download a single source file using wget into the current folder.

        Runs wget with -N so partial downloads are replace. stdout and
        stderr are suppressed to keep terminal output clean.

        Args:
            link: The URL of the source file to download.

        Returns:
            True if wget exited with return code 0, False otherwise.
        """
        rc: int = subprocess.run(
            ['wget', '-N', link], check=False,
            stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL).returncode
        return rc == 0
