"""Background job system for running AI analyses without blocking the UI."""
import asyncio
from datetime import datetime
from enum import Enum
from typing import Dict, Any, Optional, Callable, Awaitable
from uuid import UUID, uuid4
from dataclasses import dataclass, field
from loguru import logger


class JobStatus(str, Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"


class JobType(str, Enum):
    POST_SCRAPING = "post_scraping"
    PROFILE_ANALYSIS = "profile_analysis"
    POST_CATEGORIZATION = "post_categorization"
    POST_TYPE_ANALYSIS = "post_type_analysis"


@dataclass
class BackgroundJob:
    id: str
    job_type: JobType
    user_id: str
    status: JobStatus = JobStatus.PENDING
    progress: int = 0
    message: str = ""
    created_at: datetime = field(default_factory=datetime.utcnow)
    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    error: Optional[str] = None
    result: Optional[Dict[str, Any]] = None


class BackgroundJobManager:
    """Manages background jobs for AI analyses."""

    def __init__(self):
        self._jobs: Dict[str, BackgroundJob] = {}
        self._user_jobs: Dict[str, list] = {}  # user_id -> list of job_ids
        self._listeners: Dict[str, list] = {}  # user_id -> list of callbacks

    def create_job(self, job_type: JobType, user_id: str) -> BackgroundJob:
        """Create a new background job."""
        job_id = str(uuid4())
        job = BackgroundJob(
            id=job_id,
            job_type=job_type,
            user_id=user_id
        )
        self._jobs[job_id] = job

        if user_id not in self._user_jobs:
            self._user_jobs[user_id] = []
        self._user_jobs[user_id].append(job_id)

        logger.info(f"Created background job {job_id} of type {job_type} for user {user_id}")
        return job

    def get_job(self, job_id: str) -> Optional[BackgroundJob]:
        """Get a job by ID."""
        return self._jobs.get(job_id)

    def get_user_jobs(self, user_id: str) -> list[BackgroundJob]:
        """Get all jobs for a user."""
        job_ids = self._user_jobs.get(user_id, [])
        return [self._jobs[jid] for jid in job_ids if jid in self._jobs]

    def get_active_jobs(self, user_id: str) -> list[BackgroundJob]:
        """Get running/pending jobs for a user."""
        return [
            j for j in self.get_user_jobs(user_id)
            if j.status in (JobStatus.PENDING, JobStatus.RUNNING)
        ]

    async def update_job(
        self,
        job_id: str,
        status: Optional[JobStatus] = None,
        progress: Optional[int] = None,
        message: Optional[str] = None,
        error: Optional[str] = None,
        result: Optional[Dict[str, Any]] = None
    ):
        """Update a job's status and notify listeners."""
        job = self._jobs.get(job_id)
        if not job:
            return

        if status:
            job.status = status
            if status == JobStatus.RUNNING and not job.started_at:
                job.started_at = datetime.utcnow()
            elif status in (JobStatus.COMPLETED, JobStatus.FAILED):
                job.completed_at = datetime.utcnow()

        if progress is not None:
            job.progress = progress
        if message:
            job.message = message
        if error:
            job.error = error
        if result:
            job.result = result

        # Notify listeners
        await self._notify_listeners(job.user_id, job)

    async def _notify_listeners(self, user_id: str, job: BackgroundJob):
        """Notify all listeners for a user about job updates."""
        listeners = self._listeners.get(user_id, [])
        for callback in listeners:
            try:
                await callback(job)
            except Exception as e:
                logger.error(f"Error notifying listener: {e}")

    def add_listener(self, user_id: str, callback: Callable[[BackgroundJob], Awaitable[None]]):
        """Add a listener for job updates."""
        if user_id not in self._listeners:
            self._listeners[user_id] = []
        self._listeners[user_id].append(callback)

    def remove_listener(self, user_id: str, callback: Callable[[BackgroundJob], Awaitable[None]]):
        """Remove a listener."""
        if user_id in self._listeners:
            try:
                self._listeners[user_id].remove(callback)
            except ValueError:
                pass

    def cleanup_old_jobs(self, max_age_hours: int = 24):
        """Remove completed jobs older than max_age_hours."""
        cutoff = datetime.utcnow()
        to_remove = []

        for job_id, job in self._jobs.items():
            if job.completed_at:
                age = (cutoff - job.completed_at).total_seconds() / 3600
                if age > max_age_hours:
                    to_remove.append(job_id)

        for job_id in to_remove:
            job = self._jobs.pop(job_id, None)
            if job:
                user_jobs = self._user_jobs.get(job.user_id, [])
                if job_id in user_jobs:
                    user_jobs.remove(job_id)

        if to_remove:
            logger.info(f"Cleaned up {len(to_remove)} old background jobs")


# Global instance — backed by Supabase DB + Redis pub/sub for multi-worker safety.
# db_job_manager imports BackgroundJob/JobType/JobStatus from this module, so
# this import must stay at the bottom to avoid a circular-import issue.
from src.services.db_job_manager import job_manager  # noqa: F401


async def run_post_scraping(user_id: UUID, linkedin_url: str, job_id: str):
    """Run LinkedIn post scraping in background."""
    from src.database.client import DatabaseClient
    from src.scraper import scraper
    from src.database.models import LinkedInPost

    db = DatabaseClient()

    try:
        await job_manager.update_job(
            job_id,
            status=JobStatus.RUNNING,
            progress=10,
            message="Starte LinkedIn-Scraping..."
        )

        # Scrape posts
        await job_manager.update_job(
            job_id,
            progress=30,
            message="Lade Posts von LinkedIn..."
        )

        raw_posts = await scraper.scrape_posts(linkedin_url, limit=50)
        parsed_posts = scraper.parse_posts_data(raw_posts)

        await job_manager.update_job(
            job_id,
            progress=60,
            message=f"{len(parsed_posts)} Posts gefunden, speichere..."
        )

        linkedin_posts = []
        profile_picture = None

        for post_data in parsed_posts:
            post = LinkedInPost(user_id=user_id, **post_data)
            linkedin_posts.append(post)

            # Extract profile picture from first post with author data
            if not profile_picture and post_data.get("raw_data"):
                author = post_data["raw_data"].get("author", {})
                if author and isinstance(author, dict):
                    profile_picture = author.get("profile_picture")

        if linkedin_posts:
            await db.save_linkedin_posts(linkedin_posts)

        # Save profile picture to profile
        if profile_picture:
            await db.update_profile(user_id, {"profile_picture": profile_picture})

        await job_manager.update_job(
            job_id,
            status=JobStatus.COMPLETED,
            progress=100,
            message=f"{len(linkedin_posts)} Posts gespeichert!",
            result={"posts_count": len(linkedin_posts), "profile_picture": profile_picture}
        )

        logger.info(f"Post scraping completed for user {user_id}: {len(linkedin_posts)} posts")

    except Exception as e:
        logger.error(f"Post scraping failed: {e}")
        await job_manager.update_job(
            job_id,
            status=JobStatus.FAILED,
            error=str(e)
        )


async def run_profile_analysis(user_id: UUID, job_id: str):
    """Run profile analysis in background."""
    from src.database.client import DatabaseClient
    from src.agents.profile_analyzer import ProfileAnalyzerAgent
    from src.database.models import ProfileAnalysis

    db = DatabaseClient()

    try:
        await job_manager.update_job(
            job_id,
            status=JobStatus.RUNNING,
            progress=10,
            message="Lade LinkedIn-Posts..."
        )

        # Get posts and profile
        posts = await db.get_linkedin_posts(user_id)
        if not posts:
            await job_manager.update_job(
                job_id,
                status=JobStatus.FAILED,
                error="Keine Posts gefunden"
            )
            return

        linkedin_profile = await db.get_linkedin_profile(user_id)
        user_profile = await db.get_profile(user_id)

        await job_manager.update_job(
            job_id,
            progress=30,
            message=f"Analysiere {len(posts)} Posts..."
        )

        # Run analysis
        analyzer = ProfileAnalyzerAgent()

        # Prepare user data
        user_data = {
            "name": user_profile.display_name if user_profile else "",
            "company": "",
            "writing_style_notes": user_profile.writing_style_notes if user_profile else ""
        }

        # Create a minimal linkedin profile if none exists
        if not linkedin_profile:
            from src.database.models import LinkedInProfile
            linkedin_profile = LinkedInProfile(
                user_id=user_id,
                profile_data={},
                name=user_profile.display_name if user_profile else ""
            )

        analysis_result = await analyzer.process(linkedin_profile, posts, user_data)

        await job_manager.update_job(
            job_id,
            progress=80,
            message="Speichere Analyse..."
        )

        # Save analysis
        profile_analysis = ProfileAnalysis(
            user_id=user_id,
            writing_style=analysis_result.get("writing_style", {}),
            tone_analysis=analysis_result.get("tone_analysis", {}),
            topic_patterns=analysis_result.get("topic_patterns", {}),
            audience_insights=analysis_result.get("audience_insights", {}),
            full_analysis=analysis_result
        )
        await db.save_profile_analysis(profile_analysis)

        await job_manager.update_job(
            job_id,
            status=JobStatus.COMPLETED,
            progress=100,
            message="Profil-Analyse abgeschlossen!"
        )

        logger.info(f"Profile analysis completed for user {user_id}")

    except Exception as e:
        logger.error(f"Profile analysis failed: {e}")
        await job_manager.update_job(
            job_id,
            status=JobStatus.FAILED,
            error=str(e)
        )


async def run_post_categorization(user_id: UUID, job_id: str):
    """Run automatic post categorization in background."""
    from src.database.client import DatabaseClient
    from src.agents.post_classifier import PostClassifierAgent

    db = DatabaseClient()

    try:
        await job_manager.update_job(
            job_id,
            status=JobStatus.RUNNING,
            progress=10,
            message="Lade Posts und Typen..."
        )

        # Get posts and types
        posts = await db.get_unclassified_posts(user_id)
        post_types = await db.get_post_types(user_id)

        if not posts:
            await job_manager.update_job(
                job_id,
                status=JobStatus.COMPLETED,
                progress=100,
                message="Alle Posts sind bereits kategorisiert!"
            )
            return

        if not post_types:
            await job_manager.update_job(
                job_id,
                status=JobStatus.FAILED,
                error="Keine Post-Typen definiert"
            )
            return

        await job_manager.update_job(
            job_id,
            progress=30,
            message=f"Kategorisiere {len(posts)} Posts..."
        )

        # Run classification
        classifier = PostClassifierAgent()
        classifications = await classifier.process(posts, post_types)

        await job_manager.update_job(
            job_id,
            progress=70,
            message="Speichere Kategorisierungen..."
        )

        # Save classifications
        if classifications:
            await db.update_posts_classification_bulk(classifications)

        await job_manager.update_job(
            job_id,
            status=JobStatus.COMPLETED,
            progress=100,
            message=f"{len(classifications)} Posts kategorisiert!"
        )

        # Invalidate cached LinkedIn posts — classifications changed but bulk update
        # doesn't have user_id per-row, so we invalidate explicitly here.
        from src.services.cache_service import cache as _cache
        await _cache.invalidate_linkedin_posts(str(user_id))

        logger.info(f"Post categorization completed for user {user_id}: {len(classifications)} posts")

    except Exception as e:
        logger.error(f"Post categorization failed: {e}")
        await job_manager.update_job(
            job_id,
            status=JobStatus.FAILED,
            error=str(e)
        )


async def run_post_recategorization(user_id: UUID, job_id: str):
    """Re-categorize ALL LinkedIn posts (including already categorized ones)."""
    from src.database.client import DatabaseClient
    from src.agents.post_classifier import PostClassifierAgent
    import asyncio

    db = DatabaseClient()

    try:
        await job_manager.update_job(
            job_id,
            status=JobStatus.RUNNING,
            progress=5,
            message="Lade alle LinkedIn Posts..."
        )

        # Get ALL LinkedIn posts (not just unclassified)
        posts = await db.get_linkedin_posts(user_id)
        post_types = await db.get_post_types(user_id)

        if not posts:
            await job_manager.update_job(
                job_id,
                status=JobStatus.COMPLETED,
                progress=100,
                message="Keine Posts gefunden!"
            )
            return

        if not post_types:
            await job_manager.update_job(
                job_id,
                status=JobStatus.FAILED,
                error="Keine Post-Typen definiert"
            )
            return

        # FIRST: Mark all posts as uncategorized before re-categorizing
        await job_manager.update_job(
            job_id,
            progress=15,
            message=f"Markiere {len(posts)} Posts als unkategorisiert..."
        )

        # Set post_type_id to NULL for all posts
        for post in posts:
            try:
                await asyncio.to_thread(
                    lambda p=post: db.client.table("linkedin_posts").update({
                        "post_type_id": None
                    }).eq("id", str(p.id)).execute()
                )
            except Exception as e:
                logger.warning(f"Failed to unclassify post {post.id}: {e}")

        logger.info(f"Marked {len(posts)} posts as uncategorized")

        await job_manager.update_job(
            job_id,
            progress=30,
            message=f"Re-kategorisiere {len(posts)} Posts..."
        )

        # Run classification on ALL posts
        classifier = PostClassifierAgent()
        classifications = await classifier.process(posts, post_types)

        await job_manager.update_job(
            job_id,
            progress=70,
            message="Speichere neue Kategorisierungen..."
        )

        # Save classifications
        if classifications:
            await db.update_posts_classification_bulk(classifications)

        await job_manager.update_job(
            job_id,
            status=JobStatus.COMPLETED,
            progress=100,
            message=f"{len(classifications)} Posts re-kategorisiert!"
        )

        from src.services.cache_service import cache as _cache
        await _cache.invalidate_linkedin_posts(str(user_id))

        logger.info(f"Post re-categorization completed for user {user_id}: {len(classifications)} posts")

    except Exception as e:
        logger.error(f"Post re-categorization failed: {e}")
        await job_manager.update_job(
            job_id,
            status=JobStatus.FAILED,
            error=str(e)
        )


async def run_post_type_analysis(user_id: UUID, job_id: str):
    """Run post type analysis in background."""
    from src.database.client import DatabaseClient
    from src.agents.post_type_analyzer import PostTypeAnalyzerAgent

    db = DatabaseClient()

    try:
        await job_manager.update_job(
            job_id,
            status=JobStatus.RUNNING,
            progress=10,
            message="Lade Post-Typen..."
        )

        post_types = await db.get_post_types(user_id)

        if not post_types:
            await job_manager.update_job(
                job_id,
                status=JobStatus.FAILED,
                error="Keine Post-Typen definiert"
            )
            return

        analyzer = PostTypeAnalyzerAgent()
        total = len(post_types)

        for i, post_type in enumerate(post_types):
            await job_manager.update_job(
                job_id,
                progress=int(10 + (80 * i / total)),
                message=f"Analysiere '{post_type.name}'..."
            )

            # Get posts for this type
            posts = await db.get_posts_by_type(user_id, post_type.id)

            if posts:
                analysis = await analyzer.process(post_type, posts)
                await db.update_post_type_analysis(post_type.id, analysis, len(posts))

        await job_manager.update_job(
            job_id,
            status=JobStatus.COMPLETED,
            progress=100,
            message=f"{total} Post-Typen analysiert!"
        )

        logger.info(f"Post type analysis completed for user {user_id}")

    except Exception as e:
        logger.error(f"Post type analysis failed: {e}")
        await job_manager.update_job(
            job_id,
            status=JobStatus.FAILED,
            error=str(e)
        )


async def run_full_analysis_pipeline(user_id: UUID):
    """Run the full analysis pipeline in sequence: Profile -> Categorization -> Post Types."""
    logger.info(f"Starting full analysis pipeline for user {user_id}")

    # 1. Profile Analysis
    job1 = await job_manager.create_job(JobType.PROFILE_ANALYSIS, str(user_id))
    await run_profile_analysis(user_id, job1.id)

    # 2. Post Categorization (always continue regardless of previous step outcome)
    job2 = await job_manager.create_job(JobType.POST_CATEGORIZATION, str(user_id))
    await run_post_categorization(user_id, job2.id)

    # 3. Post Type Analysis
    job3 = await job_manager.create_job(JobType.POST_TYPE_ANALYSIS, str(user_id))
    await run_post_type_analysis(user_id, job3.id)

    logger.info(f"Full analysis pipeline completed for user {user_id}")
