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

import re
from pathlib import Path
from typing import Generator, Optional

from sun.configs import configs_load
from sun.utils import Utilities


class Fetcher:
    """Fetches and parses the Slackware ChangeLog to detect upgraded,
    rebuilt, added or removed packages.

    The remote ChangeLog is fetched with an If-Modified-Since header so that
    repeated calls skip the download when the file has not changed. The last
    known content is kept in a cache and reused on 304 responses or network
    errors, ensuring the caller always gets a consistent result.

    Attributes:
        _last_modified (Optional[str]): Last-Modified header value from the
            previous successful remote fetch.
        _mirror_log_cache (Optional[str]): Cached content of the remote
            ChangeLog from the most recent successful fetch.
    """

    REPO_PATTERN: str = r'\.txz:\s*(Upgraded\.|Rebuilt\.|Added\.|Removed\.)$'
    REPO_DAYS: str = r'^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'

    def __init__(self) -> None:
        self.configs = configs_load
        self.utils = Utilities()
        self._last_modified: Optional[str] = None
        self._mirror_log_cache: Optional[str] = None
        self.last_fetch_error: bool = False

    def updates(self) -> Generator[str, None, None]:
        """Yields package names that have been updated since the local ChangeLog date.

        Returns nothing if the local ChangeLog is missing (slackpkg/slpkg has
        not synced yet) or if the remote file could not be fetched.

        Yields:
            str: Package filename of each updated package.
        """
        if not (self.configs.repo_mirror and self.configs.repo_log_path):
            return

        self._fetch_mirror_if_modified()
        mirror_log = self._mirror_log_cache
        if not mirror_log:
            return

        local_log = Utilities.read_local_text_file(
            Path(self.configs.repo_log_path, self.configs.repo_log_file)
        )
        if not local_log:
            return

        local_date = self._extract_date(local_log)
        if not local_date:
            return

        for line in mirror_log.splitlines():
            if local_date == line.strip():
                break
            if re.search(self.REPO_PATTERN, line):
                line = self.patch_line_for_slackware(line)
                yield f'{line.split("/")[-1]}'

    def _fetch_mirror_if_modified(self) -> None:
        """Fetches the remote ChangeLog only if it has changed since the last fetch.

        Updates _mirror_log_cache and _last_modified on a successful response.
        On 304 Not Modified or network error the cache is left unchanged.
        """
        content, new_lm = self.utils.fetch_if_modified(self._last_modified)
        if content is not None:
            self._mirror_log_cache = content
            self._last_modified = new_lm
            self.last_fetch_error = False
        elif self._mirror_log_cache is None:
            # Fetch failed and no cached copy available.
            self.last_fetch_error = True
        else:
            # 304 Not Modified or fetch failed but cache exists — use cache.
            self.last_fetch_error = False

    def _extract_date(self, log: str) -> Optional[str]:
        """Extracts the first date line from a ChangeLog string.

        Args:
            log (str): ChangeLog content.

        Returns:
            Optional[str]: The first date line, or None if not found.
        """
        for line in log.splitlines():
            if re.match(self.REPO_DAYS, line):
                return line.strip()
        return None

    @staticmethod
    def patch_line_for_slackware(line: str) -> str:
        """Normalizes linux kernel patch lines to extract the version directory name.

        Args:
            line (str): A ChangeLog.txt entry line.

        Returns:
            str: Normalized line.
        """
        if line.startswith('patches/packages/linux'):
            line = line.split("/")[-2]
        return line
