#!/usr/bin/env python3
import logging
import os
import shutil
import tempfile
import zipfile
from datetime import datetime

import requests
from pathlib import Path
from tqdm import tqdm
from werkzeug.utils import secure_filename

logger = logging.getLogger()


class JobImportHandler:

    @classmethod
    def validate_job_data(cls, new_job_data: dict, upload_directory: Path, uploaded_file=None) -> dict:
        loaded_project_local_path = None

        # check for required keys
        job_name = new_job_data.get('name')
        engine_name = new_job_data.get('engine_name')
        if not job_name:
            raise KeyError("Missing job name")
        elif not engine_name:
            raise KeyError("Missing engine name")

        project_url = new_job_data.get('url', None)
        local_path = new_job_data.get('local_path', None)
        downloaded_file_url = None

        if uploaded_file and uploaded_file.filename:
            referred_name = os.path.basename(uploaded_file.filename)
        elif project_url:
            referred_name, downloaded_file_url = cls.download_project_from_url(project_url)
            if not referred_name:
                raise FileNotFoundError(f"Error downloading file from URL: {project_url}")
        elif local_path and os.path.exists(local_path):
            referred_name = os.path.basename(local_path)
        else:
            raise FileNotFoundError("Cannot find any valid project paths")

        # Prepare the local filepath
        cleaned_path_name = job_name.replace(' ', '-')
        timestamp = datetime.now().strftime("%Y.%m.%d_%H.%M.%S")
        folder_name = f"{cleaned_path_name}-{engine_name}-{timestamp}"
        job_dir = Path(upload_directory) / folder_name
        os.makedirs(job_dir, exist_ok=True)
        project_source_dir = Path(job_dir) / 'source'
        os.makedirs(project_source_dir, exist_ok=True)

        # Move projects to their work directories
        if uploaded_file and uploaded_file.filename:
            # Handle file uploading
            filename = secure_filename(uploaded_file.filename)
            loaded_project_local_path = Path(project_source_dir) / filename
            uploaded_file.save(str(loaded_project_local_path))
            logger.info(f"Transfer complete for {loaded_project_local_path.relative_to(upload_directory)}")

        elif project_url:
            # Handle downloading project from a URL
            loaded_project_local_path = Path(project_source_dir) / referred_name
            shutil.move(str(downloaded_file_url), str(loaded_project_local_path))
            logger.info(f"Download complete for {loaded_project_local_path.relative_to(upload_directory)}")

        elif local_path:
            # Handle local files
            loaded_project_local_path = Path(project_source_dir) / referred_name
            shutil.copy(str(local_path), str(loaded_project_local_path))
            logger.info(f"Import complete for {loaded_project_local_path.relative_to(upload_directory)}")

        if loaded_project_local_path.suffix == ".zip":
            loaded_project_local_path = cls.process_zipped_project(loaded_project_local_path)

        new_job_data["__loaded_project_local_path"] = loaded_project_local_path

        return new_job_data

    @staticmethod
    def download_project_from_url(project_url: str):
        # This nested function is to handle downloading from a URL
        logger.info(f"Downloading project from url: {project_url}")
        referred_name = os.path.basename(project_url)

        try:
            response = requests.get(project_url, stream=True)
            if response.status_code == 200:
                # Get the total file size from the "Content-Length" header
                file_size = int(response.headers.get("Content-Length", 0))
                # Create a progress bar using tqdm
                progress_bar = tqdm(total=file_size, unit="B", unit_scale=True)
                # Open a file for writing in binary mode
                downloaded_file_url = os.path.join(tempfile.gettempdir(), referred_name)
                with open(downloaded_file_url, "wb") as file:
                    for chunk in response.iter_content(chunk_size=1024):
                        if chunk:
                            # Write the chunk to the file
                            file.write(chunk)
                            # Update the progress bar
                            progress_bar.update(len(chunk))
                # Close the progress bar
                progress_bar.close()
                return referred_name, downloaded_file_url
        except Exception as e:
            logger.error(f"Error downloading file: {e}")
        return None, None

    @staticmethod
    def process_zipped_project(zip_path: Path) -> Path:
        """
        Processes a zipped project.

        This method takes a path to a zip file, extracts its contents, and returns the path to the extracted project file.
        If the zip file contains more than one project file or none, an error is raised.

        Args:
            zip_path (Path): The path to the zip file.

        Raises:
            ValueError: If there's more than 1 project file or none in the zip file.

        Returns:
            Path: The path to the main project file.
        """
        work_path = zip_path.parent

        try:
            with zipfile.ZipFile(zip_path, 'r') as myzip:
                myzip.extractall(str(work_path))

            project_files = [p for p in work_path.iterdir() if p.is_file() and p.suffix.lower() != ".zip"]

            logger.debug(f"Zip files: {project_files}")

            # supported_exts = RenderWorkerFactory.class_for_name(engine).engine.supported_extensions
            # if supported_exts:
            #     project_files = [file for file in project_files if any(file.endswith(ext) for ext in supported_exts)]

            # If there's more than 1 project file or none, raise an error
            if len(project_files) != 1:
                raise ValueError(f'Cannot find a valid project file in {zip_path.name}')

            extracted_project_path = work_path / project_files[0]
            logger.info(f"Extracted zip file to {extracted_project_path}")

        except (zipfile.BadZipFile, zipfile.LargeZipFile) as e:
            logger.error(f"Error processing zip file: {e}")
            raise ValueError(f"Error processing zip file: {e}")
        return extracted_project_path
