#!/usr/bin/env python3
"""
Debian Software Manager user downloads script

Author: Arjen Balfoort
Date: 2026-05-21
"""

import subprocess
import tempfile
import os
import glob

from configparser import ConfigParser
from email.parser import Parser as EmailParser
from typing import List, Optional

from app_shared import (
    AppStreamPackageInfo,
    DebSoftwareApp
)

class DownloadsHandler():
    """ Manages downloaded package actions. """
    def __init__(self, app: DebSoftwareApp):
        self.app: DebSoftwareApp = app

    def get_packages_info(self, paths: List[str]) -> List[AppStreamPackageInfo]:
        """Process a list of paths and extract metadata from any .deb files."""
        results = []
        for path in paths:
            info = None
            self.app.logging.info(f"Extracting metadata from {path}")
            if self._is_deb_file(path):
                info = self.extract_deb_info(path)
            elif self._is_flatpak_file(path):
                # TODO: extract flatpak info
                pass
            elif self._is_flatpakref_file(path):
                # TODO: install flatpak repo if needed and get flatpak package info
                pass
            elif self._is_flatpakrepo_file(path):
                # TODO: install flatpak repository
                pass
            else:
                self.app.logging.warning(f"Unknown file type: {path}")
            if info:
                results.append(info)
        return results

    def extract_deb_info(self, deb_path: str) -> Optional[AppStreamPackageInfo]:
        """
        Extract metadata from a .deb package file.
        Returns AppStreamPackageInfo if successful, None otherwise.
        """
        if not self._is_deb_file(deb_path):
            return None

        try:
            with tempfile.TemporaryDirectory() as tmpdir:
                # Extract control files
                control_dir = os.path.join(tmpdir, 'DEBIAN')
                os.makedirs(control_dir)
                subprocess.run(
                    ['dpkg-deb', '-e', deb_path, control_dir],
                    check=True, capture_output=True, text=True
                )

                # Parse control file
                control_data = {}
                control_file = os.path.join(control_dir, 'control')
                if os.path.exists(control_file):
                    with open(control_file, 'r', encoding='utf-8') as f:
                        control_data = self._parse_control_file(f.read())

                # Extract data files
                data_dir = os.path.join(tmpdir, 'data')
                os.makedirs(data_dir)
                subprocess.run(
                    ['dpkg-deb', '-x', deb_path, data_dir],
                    check=True, capture_output=True, text=True
                )

                # Find and parse .desktop files
                desktop_data = None
                applications_dir = os.path.join(data_dir, 'usr', 'share', 'applications')
                if os.path.exists(applications_dir):
                    desktop_files = sorted(glob.glob(os.path.join(applications_dir, '*.desktop')))
                    desktop_data = self._parse_desktop_files(desktop_files, data_dir)

                if not desktop_data:
                    return None

                # Extract fields
                package_id = control_data.get('Package', '')
                full_desc = control_data.get('Description', '')
                summary = full_desc.split('\n')[0] if full_desc else ''
                description = '\n'.join(full_desc.split('\n')[1:]).strip() if '\n' in full_desc else ''

                # Categories (filter empty strings)
                categories_str = desktop_data.get('Categories', '')
                categories = [c.strip() for c in categories_str.split(';') if c.strip()] \
                            if categories_str else None

                # Dependencies (filter empty strings)
                depends = control_data.get('Depends', '')
                additional_packages = [
                    dep.split('(')[0].strip()
                    for dep in depends.split(',')
                    if dep.strip()
                ] if depends else None

                return AppStreamPackageInfo(
                    package_id=package_id,
                    package_name=package_id,
                    name=desktop_data.get('Name', ''),
                    summary=summary,
                    description=description,
                    categories=categories,
                    origin='downloaded',
                    homepage=control_data.get('Homepage', ''),
                    icon=desktop_data.get('Icon', ''),
                    screenshots=[],
                    version=control_data.get('Version', ''),
                    section=control_data.get('Section', ''),
                    is_installed=False,
                    installed_size=control_data.get('Installed-Size', ''),
                    architecture=control_data.get('Architecture', 'amd64'),
                    candidate_version='',
                    additional_packages=additional_packages,
                    executable=desktop_data.get('Exec', ''),
                    download_path=deb_path
                )

        except subprocess.CalledProcessError as e:
            self.app.logging.error(f"dpkg-deb error for {deb_path}: {e.stderr}")
            return None
        except Exception as e: # pylint: disable=broad-except
            self.app.logging.error(f"Error processing {deb_path}: {str(e)}")
            return None

    @staticmethod
    def _parse_control_file(control_text: str) -> dict:
        """Parse a Debian control file (RFC 822 format)."""
        parser = EmailParser()
        msg = parser.parsestr(control_text)
        return {key: msg[key] for key in msg.keys()}

    def _parse_desktop_files(self, desktop_files: List[str], data_dir: str) -> Optional[dict]:
        """
        Parse .desktop files and return the first one with a matching binary.
        Returns None if no valid .desktop file is found.
        """
        for desktop_file in desktop_files:
            try:
                parser = ConfigParser(interpolation=None)
                parser.optionxform = str  # Preserve key case
                parser.read(desktop_file)
                if 'Desktop Entry' not in parser:
                    continue

                desktop_data = dict(parser['Desktop Entry'])
                exec_line = desktop_data.get('Exec', '')
                if not exec_line:
                    continue

                binary = exec_line.split()[0]
                for bin_dir in ['usr/bin', 'usr/sbin', 'usr/games']:
                    bin_path = os.path.join(data_dir, bin_dir, binary)
                    if os.path.exists(bin_path):
                        return desktop_data
            except Exception: # pylint: disable=broad-except
                self.app.logging.error(f"Error parsing {desktop_file}")
                continue
        return None

    @staticmethod
    def _is_deb_file(filepath: str) -> bool:
        """Check if a file is a Debian package by extension."""
        return os.path.isfile(filepath) and filepath.lower().endswith(('.deb', '.udeb'))

    @staticmethod
    def _is_flatpak_file(filepath: str) -> bool:
        """Check if a file is a Flatpak package by extension."""
        return os.path.isfile(filepath) and filepath.lower().endswith(('flatpak'))

    @staticmethod
    def _is_flatpakrepo_file(filepath: str) -> bool:
        """Check if a file is a Flatpak package by extension."""
        return os.path.isfile(filepath) and filepath.lower().endswith(('flatpakrepo'))

    @staticmethod
    def _is_flatpakref_file(filepath: str) -> bool:
        """Check if a file is a Flatpak package by extension."""
        return os.path.isfile(filepath) and filepath.lower().endswith(('flatpakref'))
