"""
CSV Task Orchestrator for Smart Inventory
==========================================

This module provides utilities to manage CSV processing Celery tasks with:
- Dependency ordering between CSV types (product -> purchase_order -> purchase_receive -> etc.)
- FIFO execution for same CSV type per company
- HOLD status for tasks waiting on dependencies
- Polling for HOLD tasks to start when dependencies complete

CSV Dependency Chain:
    product → [] (no dependencies)
    purchase_order → [product]
    purchase_receive → [product, purchase_order]
    sales_order → [product, purchase_order, purchase_receive]
    sales_return → [product, purchase_order, purchase_receive, sales_order]
    purchase_return → [product, purchase_order, purchase_receive]
    stock_transfer → [product, purchase_order, purchase_receive, sales_order, sales_return, purchase_return]

Usage:
    from src.smart_inventory.utils.csv_task_orchestrator import trigger_csv_processing_task
    
    # After saving CSV file
    task_id = trigger_csv_processing_task(
        db=db,
        company_id=1,
        upload_type="sales_order",
        file_path="/path/to/sales_order_1_2026-01-20_103000.csv"
    )
"""

import logging
import sys
import uuid
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Optional, Any
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)


# =============================================================================
# CSV TASK DEFINITIONS AND DEPENDENCIES
# =============================================================================

# CSV types in dependency order
CSV_UPLOAD_TYPES = [
    "product",
    "purchase_order",
    "purchase_receive",
    "sales_order",
    "sales_return",
    "purchase_return",
    "stock_transfer",
]

# CSV task dependencies: key depends on values (must complete before key can start)
CSV_TASK_DEPENDENCIES: Dict[str, List[str]] = {
    "product": [],  # No dependencies
    "purchase_order": ["product"],
    "purchase_receive": ["product", "purchase_order"],
    "sales_order": ["product", "purchase_order", "purchase_receive"],
    "sales_return": ["product", "purchase_order", "purchase_receive", "sales_order"],
    "purchase_return": ["product", "purchase_order", "purchase_receive"],
    "stock_transfer": ["product", "purchase_order", "purchase_receive", "sales_order", "sales_return", "purchase_return"],
}

# Celery task name mapping for each CSV type
CSV_TASK_NAME_MAPPING: Dict[str, str] = {
    "product": "src.smart_inventory.tasks.csv_processing_task.process_product_csv",
    "purchase_order": "src.smart_inventory.tasks.csv_processing_task.process_purchase_order_csv",
    "purchase_receive": "src.smart_inventory.tasks.csv_processing_task.process_purchase_receive_csv",
    "sales_order": "src.smart_inventory.tasks.csv_processing_task.process_sales_order_csv",
    "sales_return": "src.smart_inventory.tasks.csv_processing_task.process_sales_return_csv",
    "purchase_return": "src.smart_inventory.tasks.csv_processing_task.process_purchase_return_csv",
    "stock_transfer": "src.smart_inventory.tasks.csv_processing_task.process_stock_transfer_csv",
}


# =============================================================================
# FILE UTILITIES
# =============================================================================

# Directory paths for CSV files
UPLOAD_DIR = Path(__file__).parent.parent / "apps" / "data_import" / "uploaded_files"
ARCHIVE_DIR = Path(__file__).parent.parent / "apps" / "data_import" / "archived_files"


def get_csv_filename(upload_type: str, company_id: int) -> str:
    """
    Generate a unique filename for the CSV file.
    Format: {upload_type}_{company_id}_{YYYY-MM-DD_HHmmss}.csv
    
    Args:
        upload_type: Type of CSV upload
        company_id: Company ID
        
    Returns:
        Filename string
    """
    timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
    return f"{upload_type}_{company_id}_{timestamp}.csv"


def save_uploaded_file(file_content: bytes, upload_type: str, company_id: int) -> str:
    """
    Save uploaded CSV file to the uploads directory.
    
    Args:
        file_content: Raw file content bytes
        upload_type: Type of CSV upload
        company_id: Company ID
        
    Returns:
        Absolute path to the saved file
    """
    # Ensure upload directory exists
    UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
    
    filename = get_csv_filename(upload_type, company_id)
    file_path = UPLOAD_DIR / filename
    
    with open(file_path, "wb") as f:
        f.write(file_content)
    
    logger.info(f"Saved CSV file: {file_path}")
    return str(file_path)


def archive_file(file_path: str, failed: bool = False) -> Optional[str]:
    """
    Move processed CSV file to the archive directory.
    
    Args:
        file_path: Path to the file to archive
        failed: If True, append '_failed' suffix to filename
        
    Returns:
        Path to archived file, or None if failed
    """
    try:
        source = Path(file_path)
        if not source.exists():
            logger.warning(f"File not found for archiving: {file_path}")
            return None
        
        # Ensure archive directory exists
        ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
        
        # Add _failed suffix if processing failed
        if failed:
            dest_name = source.stem + "_failed" + source.suffix
        else:
            dest_name = source.name
        
        dest = ARCHIVE_DIR / dest_name
        
        # Handle duplicate filenames by adding counter
        counter = 1
        while dest.exists():
            if failed:
                dest_name = f"{source.stem}_failed_{counter}{source.suffix}"
            else:
                dest_name = f"{source.stem}_{counter}{source.suffix}"
            dest = ARCHIVE_DIR / dest_name
            counter += 1
        
        source.rename(dest)
        logger.info(f"Archived CSV file: {dest}")
        return str(dest)
    
    except Exception as e:
        logger.error(f"Failed to archive file {file_path}: {e}")
        return None


# =============================================================================
# DEPENDENCY CHECKING FUNCTIONS
# =============================================================================

def check_csv_dependencies_met(
    db: Session,
    company_id: int,
    upload_type: str
) -> bool:
    """
    Check if all dependencies for a CSV task have completed successfully.
    
    A CSV task can only start when ALL tasks of dependent types for the same
    company are complete (SUCCESS status). This includes tasks from any batch,
    not just the current batch.
    
    Args:
        db: Database session
        company_id: Company ID
        upload_type: The CSV upload type to check dependencies for
        
    Returns:
        True if all dependencies are met (no pending/started/hold tasks of dependent types)
    """
    from src.smart_inventory.apps.inventory.models import CeleryTaskTracker, CeleryTaskStatus
    
    dependencies = CSV_TASK_DEPENDENCIES.get(upload_type, [])
    
    if not dependencies:
        return True
    
    # Check for any non-complete tasks of dependent types
    pending_statuses = [
        CeleryTaskStatus.PENDING,
        CeleryTaskStatus.STARTED,
        CeleryTaskStatus.HOLD,
    ]
    
    for dep_type in dependencies:
        pending_dep_tasks = db.query(CeleryTaskTracker).filter(
            CeleryTaskTracker.company_id == company_id,
            CeleryTaskTracker.upload_type == dep_type,
            CeleryTaskTracker.status.in_(pending_statuses)
        ).first()
        
        if pending_dep_tasks:
            logger.debug(
                f"CSV {upload_type} waiting for {dep_type} "
                f"(task: {pending_dep_tasks.task_name}, status: {pending_dep_tasks.status.value})"
            )
            return False
    
    return True


def check_same_type_fifo(
    db: Session,
    company_id: int,
    upload_type: str,
    current_task_id: str
) -> bool:
    """
    Check if this task can run based on FIFO ordering for same upload type.
    
    A CSV task can only start when no earlier tasks of the SAME type for the
    same company are still pending/started/hold.
    
    Args:
        db: Database session
        company_id: Company ID
        upload_type: The CSV upload type
        current_task_id: The current task's ID
        
    Returns:
        True if no earlier same-type tasks are pending
    """
    from src.smart_inventory.apps.inventory.models import CeleryTaskTracker, CeleryTaskStatus
    
    pending_statuses = [
        CeleryTaskStatus.PENDING,
        CeleryTaskStatus.STARTED,
        CeleryTaskStatus.HOLD,
    ]
    
    # Get current task to find its created_at
    current_task = db.query(CeleryTaskTracker).filter(
        CeleryTaskTracker.task_id == current_task_id
    ).first()
    
    if not current_task:
        return True
    
    # Check for earlier tasks of same type that are not complete
    earlier_tasks = db.query(CeleryTaskTracker).filter(
        CeleryTaskTracker.company_id == company_id,
        CeleryTaskTracker.upload_type == upload_type,
        CeleryTaskTracker.status.in_(pending_statuses),
        CeleryTaskTracker.created_at < current_task.created_at,
        CeleryTaskTracker.task_id != current_task_id
    ).first()
    
    if earlier_tasks:
        logger.debug(
            f"CSV {upload_type} task {current_task_id} waiting for earlier task "
            f"{earlier_tasks.task_id} (FIFO)"
        )
        return False
    
    return True


# =============================================================================
# TASK DISPATCH FUNCTIONS
# =============================================================================

def _get_csv_celery_task(upload_type: str):
    """Get the actual Celery task function by upload type (for Windows)"""
    try:
        if upload_type == "product":
            from src.smart_inventory.tasks.csv_processing_task import process_product_csv
            return process_product_csv
        elif upload_type == "purchase_order":
            from src.smart_inventory.tasks.csv_processing_task import process_purchase_order_csv
            return process_purchase_order_csv
        elif upload_type == "purchase_receive":
            from src.smart_inventory.tasks.csv_processing_task import process_purchase_receive_csv
            return process_purchase_receive_csv
        elif upload_type == "sales_order":
            from src.smart_inventory.tasks.csv_processing_task import process_sales_order_csv
            return process_sales_order_csv
        elif upload_type == "sales_return":
            from src.smart_inventory.tasks.csv_processing_task import process_sales_return_csv
            return process_sales_return_csv
        elif upload_type == "purchase_return":
            from src.smart_inventory.tasks.csv_processing_task import process_purchase_return_csv
            return process_purchase_return_csv
        elif upload_type == "stock_transfer":
            from src.smart_inventory.tasks.csv_processing_task import process_stock_transfer_csv
            return process_stock_transfer_csv
    except ImportError as e:
        logger.warning(f"Could not import CSV task for {upload_type}: {e}")
        return None
    return None


def _dispatch_csv_celery_task(
    upload_type: str,
    task_id: str,
    company_id: int,
    file_path: str
) -> bool:
    """
    Dispatch a CSV processing Celery task with platform-specific handling.
    
    Windows (solo pool): Uses direct task import and apply_async
    Linux (prefork pool): Uses send_task with task name string
    
    Args:
        upload_type: The CSV upload type
        task_id: Task ID to use
        company_id: Company ID
        file_path: Path to the CSV file
        
    Returns:
        True if task was dispatched successfully
    """
    try:
        task_kwargs = {
            "company_id": company_id,
            "file_path": file_path,
        }
        
        if sys.platform == 'win32':
            # Windows: use direct import and apply_async
            celery_task = _get_csv_celery_task(upload_type)
            if celery_task:
                celery_task.apply_async(kwargs=task_kwargs, task_id=task_id)
                logger.info(f"Dispatched CSV task {upload_type} (Windows): {task_id}")
                return True
        else:
            # Linux: use send_task with task name for prefork pool compatibility
            from src.utils.celery_worker import celery_app
            celery_name = CSV_TASK_NAME_MAPPING.get(upload_type)
            if celery_name:
                celery_app.send_task(celery_name, kwargs=task_kwargs, task_id=task_id)
                logger.info(f"Dispatched CSV task {upload_type} (Linux): {task_id}")
                return True
        
        return False
    except Exception as e:
        logger.error(f"Failed to dispatch CSV task {upload_type}: {e}")
        return False


# =============================================================================
# MAIN ORCHESTRATION FUNCTIONS
# =============================================================================

def trigger_csv_processing_task(
    db: Session,
    company_id: int,
    upload_type: str,
    file_path: str
) -> Dict[str, Any]:
    """
    Create a CSV processing task and trigger it if dependencies are met.
    
    This is the main entry point for triggering CSV processing. Call this after
    saving the uploaded CSV file.
    
    Args:
        db: Database session
        company_id: Company ID
        upload_type: Type of CSV (product, purchase_order, etc.)
        file_path: Path to the saved CSV file
        
    Returns:
        Dict with task information:
        {
            "task_id": str,
            "task_name": str,
            "status": str,  # "pending" or "hold"
            "file_path": str
        }
    """
    from src.smart_inventory.apps.inventory.models import CeleryTaskTracker, CeleryTaskStatus
    
    # Generate unique task ID and name
    task_id = str(uuid.uuid4())
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    task_name = f"csv_{upload_type}_{company_id}_{timestamp}"
    
    # Check dependencies
    dependencies_met = check_csv_dependencies_met(db, company_id, upload_type)
    
    # Determine initial status
    initial_status = CeleryTaskStatus.PENDING if dependencies_met else CeleryTaskStatus.HOLD
    
    # Create tracker record
    tracker = CeleryTaskTracker(
        task_id=task_id,
        task_name=task_name,
        company_id=company_id,
        status=initial_status,
        file_path=file_path,
        upload_type=upload_type,
        type="csv_import",  # Mark as CSV import task
        started_at=datetime.now() if dependencies_met else None
    )
    db.add(tracker)
    db.commit()
    
    logger.info(
        f"Created CSV task: {task_name} ({upload_type}) - status: {initial_status.value}"
    )
    
    # If dependencies are met, dispatch the task
    if dependencies_met:
        if _dispatch_csv_celery_task(upload_type, task_id, company_id, file_path):
            logger.info(f"CSV task {task_name} dispatched to Celery")
        else:
            # Failed to dispatch - mark as failure
            tracker.status = CeleryTaskStatus.FAILURE
            tracker.error_message = "Failed to dispatch task to Celery"
            db.commit()
    else:
        logger.info(f"CSV task {task_name} set to HOLD (waiting for dependencies)")
        # Ensure the hold poller is running
        _ensure_csv_hold_poller_running(db, company_id)
    
    return {
        "task_id": task_id,
        "task_name": task_name,
        "status": initial_status.value,
        "file_path": file_path
    }


def process_hold_csv_tasks(
    db: Session,
    company_id: Optional[int] = None
) -> int:
    """
    Check HOLD CSV tasks and start them if dependencies are met.
    
    Args:
        db: Database session
        company_id: Optional filter by company
        
    Returns:
        Number of tasks started
    """
    from src.smart_inventory.apps.inventory.models import CeleryTaskTracker, CeleryTaskStatus
    
    # Query for HOLD CSV tasks
    query = db.query(CeleryTaskTracker).filter(
        CeleryTaskTracker.status == CeleryTaskStatus.HOLD,
        CeleryTaskTracker.upload_type.isnot(None)  # Only CSV tasks have upload_type
    ).order_by(CeleryTaskTracker.created_at)  # FIFO order
    
    if company_id:
        query = query.filter(CeleryTaskTracker.company_id == company_id)
    
    hold_tasks = query.all()
    
    if not hold_tasks:
        return 0
    
    tasks_started = 0
    
    for tracker in hold_tasks:
        # Check dependencies
        if not check_csv_dependencies_met(db, tracker.company_id, tracker.upload_type):
            continue
        
        # Check FIFO ordering
        if not check_same_type_fifo(db, tracker.company_id, tracker.upload_type, tracker.task_id):
            continue
        
        # Dependencies met and FIFO order OK - dispatch the task
        if _dispatch_csv_celery_task(
            tracker.upload_type,
            tracker.task_id,
            tracker.company_id,
            tracker.file_path
        ):
            tracker.status = CeleryTaskStatus.PENDING
            tracker.started_at = datetime.now()
            db.commit()
            tasks_started += 1
            logger.info(f"Started HOLD CSV task: {tracker.task_name}")
        else:
            tracker.status = CeleryTaskStatus.FAILURE
            tracker.error_message = "Failed to dispatch task to Celery"
            db.commit()
    
    return tasks_started


def has_hold_csv_tasks(db: Session, company_id: Optional[int] = None) -> bool:
    """Check if there are any CSV tasks in HOLD status"""
    from src.smart_inventory.apps.inventory.models import CeleryTaskTracker, CeleryTaskStatus
    
    query = db.query(CeleryTaskTracker).filter(
        CeleryTaskTracker.status == CeleryTaskStatus.HOLD,
        CeleryTaskTracker.upload_type.isnot(None)
    )
    
    if company_id:
        query = query.filter(CeleryTaskTracker.company_id == company_id)
    
    return query.first() is not None


def _ensure_csv_hold_poller_running(db: Session, company_id: int):
    """
    Ensure the background poller for HOLD tasks is running.
    Uses the existing hold_task_poller which now handles both CSV and snapshot tasks.
    """
    try:
        if sys.platform == 'win32':
            # Windows: use direct import and .delay()
            from src.smart_inventory.tasks.hold_task_poller import poll_hold_tasks
            poll_hold_tasks.delay(company_id=company_id)
        else:
            # Linux: use send_task with task name for prefork pool compatibility
            from src.utils.celery_worker import celery_app
            celery_app.send_task(
                'src.smart_inventory.tasks.hold_task_poller.poll_hold_tasks',
                kwargs={"company_id": company_id}
            )
        logger.info(f"Started hold poller for company {company_id}")
    except Exception as e:
        logger.warning(f"Failed to start hold task poller: {e}")


# =============================================================================
# TASK STATUS UPDATE HELPERS
# =============================================================================

def update_csv_task_status(
    task_id: str,
    status: str,
    error_message: Optional[str] = None
):
    """
    Update CeleryTaskTracker status for a CSV processing task.
    Call this from within Celery tasks to update their tracking status.
    
    When status is 'success', automatically triggers processing of HOLD tasks
    that may have been waiting for this task to complete.
    
    Args:
        task_id: The Celery task ID
        status: New status (started, success, failure)
        error_message: Optional error message for failure status
    """
    try:
        from src.utils.db import get_db_session
        from src.smart_inventory.apps.inventory.models import CeleryTaskTracker, CeleryTaskStatus
        
        db = get_db_session()
        try:
            tracker = db.query(CeleryTaskTracker).filter(
                CeleryTaskTracker.task_id == task_id
            ).first()
            
            if tracker:
                if status == "started":
                    tracker.status = CeleryTaskStatus.STARTED
                    tracker.started_at = datetime.now()
                elif status == "success":
                    tracker.status = CeleryTaskStatus.SUCCESS
                    tracker.completed_at = datetime.now()
                elif status == "failure":
                    tracker.status = CeleryTaskStatus.FAILURE
                    tracker.completed_at = datetime.now()
                    tracker.error_message = error_message
                
                db.commit()
                logger.debug(f"Updated CSV task {task_id} status to {status}")
                
                # On success, try to start any waiting tasks
                if status == "success" and tracker.company_id:
                    started = process_hold_csv_tasks(db, tracker.company_id)
                    if started > 0:
                        logger.info(f"Started {started} HOLD CSV tasks after {tracker.task_name} completed")
            else:
                logger.warning(f"CSV task tracker not found: {task_id}")
        finally:
            db.close()
    except Exception as e:
        logger.error(f"Failed to update CSV task tracker status: {e}")
