import logging
import re
import threading

import requests

from src.engines.blender.blender_engine import Blender
from src.engines.core.base_downloader import EngineDownloader
from src.utilities.misc_helper import current_system_os, current_system_cpu

url = "https://download.blender.org/release/"

logger = logging.getLogger()
supported_formats = ['.zip', '.tar.xz', '.dmg']


class BlenderDownloader(EngineDownloader):

    engine = Blender

    @staticmethod
    def __get_major_versions():
        try:
            response = requests.get(url, timeout=5)
            response.raise_for_status()

            # Use regex to find all the <a> tags and extract the href attribute
            link_pattern = r'<a href="([^"]+)">Blender(\d+[^<]+)</a>'
            link_matches = re.findall(link_pattern, response.text)

            major_versions = [link[-1].strip('/') for link in link_matches]
            major_versions.sort(reverse=True)
            return major_versions
        except requests.exceptions.RequestException as e:
            logger.error(f"Error: {e}")
        return []

    @staticmethod
    def __get_minor_versions(major_version, system_os=None, cpu=None):

        try:
            base_url = url + 'Blender' + major_version
            response = requests.get(base_url, timeout=5)
            response.raise_for_status()

            versions_pattern = \
                r'<a href="(?P<file>[^"]+)">blender-(?P<version>[\d\.]+)-(?P<system_os>\w+)-(?P<cpu>\w+).*</a>'
            versions_data = [match.groupdict() for match in re.finditer(versions_pattern, response.text)]

            # Filter to just the supported formats
            versions_data = [item for item in versions_data if any(item["file"].endswith(ext) for ext in
                                                                   supported_formats)]

            # Filter down OS and CPU
            system_os = system_os or current_system_os()
            cpu = cpu or current_system_cpu()
            versions_data = [x for x in versions_data if x['system_os'] == system_os]
            versions_data = [x for x in versions_data if x['cpu'] == cpu]

            for v in versions_data:
                v['url'] = base_url + '/' + v['file']

            versions_data = sorted(versions_data, key=lambda x: x['version'], reverse=True)
            return versions_data
        except requests.exceptions.HTTPError as e:
            logger.error(f"Invalid url: {e}")
        except Exception as e:
            logger.exception(e)
        return []

    @staticmethod
    def __find_LTS_versions():
        response = requests.get('https://www.blender.org/download/lts/')
        response.raise_for_status()

        lts_pattern = r'https://www.blender.org/download/lts/(\d+-\d+)/'
        lts_matches = re.findall(lts_pattern, response.text)
        lts_versions = [ver.replace('-', '.') for ver in list(set(lts_matches))]
        lts_versions.sort(reverse=True)

        return lts_versions

    @classmethod
    def all_versions(cls, system_os=None, cpu=None):
        majors = cls.__get_major_versions()
        all_versions = []
        threads = []
        results = [[] for _ in majors]

        def thread_function(major_version, index, system_os_t, cpu_t):
            results[index] = cls.__get_minor_versions(major_version, system_os_t, cpu_t)

        for i, m in enumerate(majors):
            thread = threading.Thread(target=thread_function, args=(m, i, system_os, cpu))
            threads.append(thread)
            thread.start()

        # Wait for all threads to complete
        for thread in threads:
            thread.join()

        # Extend all_versions with the results from each thread
        for result in results:
            all_versions.extend(result)

        return all_versions

    @classmethod
    def find_most_recent_version(cls, system_os=None, cpu=None, lts_only=False):
        try:
            major_version = cls.__find_LTS_versions()[0] if lts_only else cls.__get_major_versions()[0]
            most_recent = cls.__get_minor_versions(major_version=major_version, system_os=system_os, cpu=cpu)
            return most_recent[0]
        except (IndexError, requests.exceptions.RequestException):
            logger.error(f"Cannot get most recent version of blender")
        return {}

    @classmethod
    def version_is_available_to_download(cls, version, system_os=None, cpu=None):
        requested_major_version = '.'.join(version.split('.')[:2])
        minor_versions = cls.__get_minor_versions(requested_major_version, system_os, cpu)
        for minor in minor_versions:
            if minor['version'] == version:
                return minor
        return None

    @classmethod
    def download_engine(cls, version, download_location, system_os=None, cpu=None, timeout=120, progress_callback=None):
        system_os = system_os or current_system_os()
        cpu = cpu or current_system_cpu()

        try:
            logger.info(f"Requesting download of blender-{version}-{system_os}-{cpu}")
            major_version = '.'.join(version.split('.')[:2])
            minor_versions = [x for x in cls.__get_minor_versions(major_version, system_os, cpu) if
                              x['version'] == version]
            cls.download_and_extract_app(remote_url=minor_versions[0]['url'], download_location=download_location,
                                         timeout=timeout, progress_callback=progress_callback)
        except IndexError:
            logger.error("Cannot find requested engine")


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    print(BlenderDownloader.find_most_recent_version())
