# tasks.py
import asyncio
import json
import requests
from celery import shared_task, group, chord, chain
from celery.utils.log import get_task_logger
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .process import ScannerProcess, ReplayProcess, redisDB
from .export_tasks import shm_download_video, export_images_zip, export_video_mp4
from .constants import ScannerConstants
from . import models

logger = get_task_logger(__name__)


@shared_task
def scanner_start():
    scanner = ScannerProcess()        
    scanner.start()     
    return str(_("Scanner démarré."))

@shared_task
def replay_start():
    replay = ReplayProcess(latency=5.0)
    replay.start()    
    return str(_("Replay démarré."))


@shared_task(bind=True)
def download_video(self, uuid, start_ts, end_ts, frame_rate=5, opencv_fourcc_format='mp4v', opencv_video_type='mp4'):
    """
    Exécuter directement la fonction async DANS la tâche Celery.
    Sans passer par une wrapper.
    """
    try:       
        logger.info(f"Task {self.request.id}: Processing {uuid} from {start_ts} to {end_ts}")
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            result = loop.run_until_complete(
                shm_download_video(uuid, start_ts, end_ts, frame_rate, opencv_fourcc_format, opencv_video_type) )
            return result
        finally:
            loop.close()
    except Exception as e:
        logger.error(f"shm_download_video_task error: {e}", exc_info=True)
        return { 'status': 'error', 'error': str(e), 'uuid': uuid}        
        
@shared_task
def export_images(
        uuid: str, 
        start_ts: float | None = None, 
        end_ts: float | None = None, 
        jpeg_quality: int = 90,
        max_zip_size_mb: int = 0, 
        max_image_width: int = 0,  
        max_image_height: int = 0 ):
    try:
        return asyncio.run(export_images_zip(uuid, start_ts, end_ts, jpeg_quality, max_zip_size_mb, max_image_width, max_image_height))
    except Exception as e:
        logger.error(f"export_images: {e}")
 

@shared_task
def export_all_images(session_id=None):
    try:
        conf = ScannerConstants().get()
        
        if session_id is None:
            sessions = [s.id for s in models.Session.objects.filter(active=False)]
        else:
            sessions = [session_id]
            
        for session_id in sessions:      
            uuid_list = models.SessionExperiment.uuid_from_session(session_id)
            job_zip = []
            for uuid in uuid_list:
                
                job = export_images.delay(  # @UndefinedVariable
                    uuid, 
                    start_ts=None,
                    end_ts=None,
                    jpeg_quality=conf.video_jpeg_quality,   # Qualité JPEG
                )
                job_zip.append(job.id)        
        
        return job_zip
    except Exception as e:
        logger.error(f"export_images: {e}")


@shared_task
def export_videos(uuid: str, start_ts: float | None = None, end_ts: float | None = None, frame_rate: int = 5,
        opencv_fourcc_format='mp4v', 
        opencv_video_type='mp4',
        max_video_size_mb: int = 0, 
        max_width: int = 0,
        max_height: int = 0, ):
    try:
        return asyncio.run(export_video_mp4(uuid, start_ts, end_ts, frame_rate, opencv_fourcc_format, opencv_video_type, max_video_size_mb, max_width, max_height))
    except Exception as e:
        logger.error(f"export_videos: {e}")
        return {"success": False, "error": str(e)}


@shared_task
def export_all_videos(session_id=None):
    try:
        conf = ScannerConstants().get()
        if session_id is None:
            sessions = [s.id for s in models.Session.objects.filter(active=False)]
        else:
            sessions = [session_id]
            
        for session_id in sessions:
            uuid_list = models.SessionExperiment.uuid_from_session(session_id)  
            job_mp4 = []
            for uuid in uuid_list:
                
                job = export_videos.delay(  # @UndefinedVariable
                    uuid, 
                    start_ts=None,
                    end_ts=None,
                    frame_rate=conf.video_frame_rate,                  # Frame rate de la vidéo exportée
                    opencv_fourcc_format=conf.opencv_fourcc_format,    # Format de compression vidéo (ex: 'mp4v' pour MP4),
                    opencv_video_type=conf.opencv_video_type,          # Type de vidéo exportée (ex: 'mp4', 'avi', 'mkv'),    
                )
                job_mp4.append(job.id)
              
        return job_mp4
    except Exception as e:
        logger.error(f"export_all_videos: {e}")


@shared_task(bind=True)
def run_session_exports(self, session_id: str):
    """
    Orchestre l'export images + vidéo en parallèle via chord.
    Lance en parallèle l'export images et l'export vidéo de la session.
    Le callback on_exports_done est appelé quand les 2 sont terminés.
    """
    try:
        session = models.Session.objects.get(pk=session_id)
    except models.Session.DoesNotExist:
        logger.error("run_session_exports: session %s introuvable", session_id)
        return {"status": "error", "message": "Session introuvable"}

    session.status = models.Session.Status.RUNNING
    session.save(update_fields=["export_status"])
    logger.info(f"run_session_exports [{session_id}]: export démarré")
    chord(
        group(
            export_all_images.s(session_id),
            export_all_videos.s(session_id),
        ),
        on_exports_done.s(session_id=session_id)
    ).apply_async()


@shared_task
def on_exports_done(results: list, session_id: str):
    """
    Callback appelé par chord quand export_all_images ET export_all_videos
    sont tous les deux terminés.
    results = [résultat_images, résultat_vidéo]
    """
    session = models.Session.objects.get(pk=session_id)

    errors = [r for r in results if isinstance(r, dict) and r.get("export_status") == "error"]
    if errors:
        session.export_status = models.Session.Status.ERROR
        logger.error(f"on_exports_done [{session_id}]: échec avec {len(errors)} erreurs")
    else:
        session.export_status = models.Session.Status.DONE
        logger.warning(f"on_exports_done [{session_id}]: succès export terminé")
    
    session.export_exported_at = timezone.now()
    session.save(update_fields=["export_status", "export_exported_at"])
    return {"status": session.export_status}


@shared_task
def scanning(session_id: str):
    """
    Scanning différé.
    """    
    try:
        logger.warning("==== scanning session %s start", session_id)
        group = 'scanner_proc'
        data = { "type": "scanner", "topic": "scan", "session": session_id }
        redisDB.publish(group, json.dumps(data))
    except Exception as e:
        logger.error(f"scanning session: {session_id} error {e}")
        return {"status": "error", "message": str(e)}

@shared_task(bind=True)
def run_scanning(self, session_id: str):   
    try:
        session = models.Session.objects.get(pk=session_id)
        if not session.active:
            raise Exception("La session n'est plus active")
    except models.Session.DoesNotExist:
        logger.error("run_session_exports: session %s introuvable", session_id)
        return {"status": "error", "message": "Session introuvable"}

    session.scanning_status = models.Session.Status.RUNNING
    session.save(update_fields=["scanning_status"])
    chain(
        scanning.s(session_id), 
        on_scanning_done.s(session_id=session_id),
    ).apply_async()

    
@shared_task
def on_scanning_done(result: dict, session_id: str):
    """
    Callback appelé automatiquement à la fin de scanning().
    Met à jour scanning_status en base — détectable par polling.
    """
    try:
        session = models.Session.objects.get(pk=session_id)
    except models.Session.DoesNotExist:
        logger.error("on_scanning_done: session %s introuvable", session_id)
        return

    session.scanning_status = models.Session.Status.ERROR if result.get("status") == "error" else models.Session.Status.DONE
    session.scanning_finished_at = timezone.now()
    session.save(update_fields=["scanning_status", "scanning_finished_at"])
    
    logger.info("==== Scanning done: session %s", session_id)
    
    
@shared_task
def http_request(req_type='json_post', url=None, user=None, passwd=None, headers={}, **kwargs):

    def json_post():
        return requests.post(url, json=kwargs, headers=headers, auth=(user, passwd), timeout=3)

    def post():
        return requests.post(url, params=kwargs, headers=headers, auth=(user, passwd), timeout=3)

    def get():
        return requests.get(url, params=kwargs, headers=headers, auth=(user, passwd), timeout=3)

    request = {
        'json_post': json_post,
        'post': post,
        'get':get
    }
    retries = 3
    for attempt in range(retries):
        try:
            response = request[req_type]()
            response.raise_for_status()
            try:
                data = response.json()
            except ValueError:
                data = response.text

            resp = {"success": True, "data": data}
            logger.info(data)
            return resp

        except requests.exceptions.Timeout:
            logger.error(f"{attempt + 1}/{retries}: Délai d'attente dépassé")
        except requests.exceptions.ConnectionError:
            logger.error(f"{attempt + 1}/{retries}: Erreur de connexion")
        except requests.exceptions.HTTPError as e:
            raise
        except Exception as e:
            logger.error(f"Erreur inattendue: {str(e)}")  
            
@shared_task
def supervisor_restart_service(params):
    """
    Redémarre un service.
    
    """
    try:
        logger.warning("==== Supervisor scanner restart all services")
        http_request(
            req_type='get',
            url=f'http://localhost:9001/{params}',
            user='root',
            passwd='toor',
            headers={}
        )
    except Exception as e:
        logger.error(f"supervisor_restart_all_services error {e}")


    