"""Workflow engine for executing multi-step tasks."""

from __future__ import annotations

import re
import time
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Callable, Optional

from ..utils.logger import Logger
from .draft_manager import DraftContent
from .intent_analyzer import WorkflowAnalysis, WorkflowStep

if TYPE_CHECKING:
    from .api_client import ApiClient
    from .auth_manager import AuthManager
    from .brand_checker import BrandChecker
    from .content_remixer import ContentRemixer
    from .draft_manager import DraftManager
    from .skill_registry import SkillRegistry
    from ..providers.provider_manager import ProviderManager


@dataclass
class WorkflowResult:
    step: WorkflowStep
    output: str
    duration: float
    success: bool
    error: Optional[str] = None


@dataclass
class WorkflowContext:
    original_input: str
    steps: list[WorkflowResult]
    shared_data: dict[str, Any] = field(default_factory=dict)


class WorkflowEngine:
    """Engine for executing workflows."""

    def __init__(
        self,
        skill_registry: "SkillRegistry",
        provider_manager: "ProviderManager",
        auth_manager: "AuthManager",
    ) -> None:
        self.skill_registry = skill_registry
        self.provider_manager = provider_manager
        self.auth_manager = auth_manager
        self.on_progress: Optional[Callable[[int, int, str], None]] = None
        self.on_step_complete: Optional[Callable[[WorkflowResult], None]] = None

        # Initialize new components
        from ..core.api_client import ApiClient

        api_key = auth_manager.get_api_key()
        self.api_client: Optional["ApiClient"] = ApiClient(api_key) if api_key else None

        if self.api_client:
            from .brand_checker import BrandChecker
            from .content_remixer import ContentRemixer
            from .draft_manager import DraftManager

            self.brand_checker = BrandChecker(self.api_client)
            self.content_remixer = ContentRemixer(self.api_client)

            agent_id = auth_manager.get_agent_id()
            self.draft_manager = DraftManager(self.api_client, agent_id) if agent_id else None
        else:
            self.brand_checker = None
            self.content_remixer = None
            self.draft_manager = None

    def set_callbacks(
        self,
        on_progress: Optional[Callable[[int, int, str], None]] = None,
        on_step_complete: Optional[Callable[[WorkflowResult], None]] = None,
    ) -> None:
        """Set progress callbacks."""
        self.on_progress = on_progress
        self.on_step_complete = on_step_complete

    def execute_workflow(
        self, analysis: WorkflowAnalysis, original_input: str
    ) -> list[WorkflowResult]:
        """Execute a workflow."""
        Logger.info("Starting workflow execution", {"stepCount": len(analysis.steps)})

        context = WorkflowContext(original_input=original_input, steps=[])
        results = []
        sorted_steps = sorted(analysis.steps, key=lambda s: s.order)

        for i, step in enumerate(sorted_steps):
            if self.on_progress:
                self.on_progress(i + 1, len(sorted_steps), step.skill)

            Logger.info(f"Executing step {i + 1}/{len(sorted_steps)}", {"skill": step.skill})

            try:
                result = self._execute_step(step, context, i + 1, len(sorted_steps))
                results.append(result)
                context.steps.append(result)
                self._extract_shared_data(step.skill, result.output, context.shared_data)

                if self.on_step_complete:
                    self.on_step_complete(result)

            except Exception as e:
                Logger.error(f"Step {step.order} failed", e)
                failed_result = WorkflowResult(
                    step=step, output="", duration=0, success=False, error=str(e)
                )
                results.append(failed_result)

        Logger.info(
            "Workflow execution complete",
            {"totalSteps": len(results), "successCount": sum(1 for r in results if r.success)},
        )

        return results

    def _execute_step(
        self, step: WorkflowStep, context: WorkflowContext, current_step_num: int, total_steps: int
    ) -> WorkflowResult:
        """Execute a single step."""
        start_time = time.time()

        # Handle special workflow steps
        if step.skill == "content_import":
            return self._execute_content_import(step, context, start_time)
        elif step.skill == "content_remix":
            return self._execute_content_remix(step, context, start_time)
        elif step.skill == "brand_consistency_check":
            return self._execute_brand_check(step, context, start_time)
        elif step.skill == "draft_submission":
            return self._execute_draft_submission(step, context, start_time)

        # Standard skill execution via provider
        skill_content = ""
        try:
            skill_content = self.skill_registry.load_skill_content(step.skill)
        except Exception:
            Logger.warn(f"Could not load skill content for {step.skill}, using default")

        previous_outputs = self._build_step_context(step, context)
        enhanced_task = (
            f"{step.task}\n\nContext from previous steps:\n{previous_outputs}"
            if previous_outputs
            else step.task
        )

        output = self.provider_manager.execute(
            skill_content,
            enhanced_task,
            {
                "skill": step.skill,
                "credentials": self.auth_manager.get_credentials(),
                "stepNumber": current_step_num,
                "totalSteps": total_steps,
            },
        )

        duration = time.time() - start_time
        return WorkflowResult(step=step, output=output, duration=duration, success=True)

    def _execute_content_import(
        self, step: WorkflowStep, context: WorkflowContext, start_time: float
    ) -> WorkflowResult:
        """Execute content import from URL."""
        if not self.content_remixer:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="Content remixer not available"
            )

        # Extract URL from task - clean up whitespace/newlines first
        task_clean = step.task.replace('\n', ' ').replace('\r', ' ')
        url_match = re.search(r'https?://[^\s"\'<>]+', task_clean)
        if not url_match:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="No URL found in task"
            )

        url = url_match.group().strip()
        content = self.content_remixer.extract_from_url(url)

        if content:
            # Store in shared data for later steps
            context.shared_data["imported_content"] = content
            context.shared_data["imported_url"] = url

            output = f"Successfully imported content from {url}\n\nTitle: {content.title}\n\nPreview:\n{content.text[:500]}..."
            duration = time.time() - start_time
            return WorkflowResult(step=step, output=output, duration=duration, success=True)

        return WorkflowResult(
            step=step, output="", duration=0, success=False, error=f"Failed to import from {url}"
        )

    def _execute_content_remix(
        self, step: WorkflowStep, context: WorkflowContext, start_time: float
    ) -> WorkflowResult:
        """Execute content remixing."""
        if not self.content_remixer:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="Content remixer not available"
            )

        # Get imported content from previous step
        imported_content = context.shared_data.get("imported_content")
        if not imported_content:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="No imported content found"
            )

        # Determine angle from task or use default
        angle_id = "angle_preset_crypto_simplified"  # default
        angle_name = "Crypto Simplified"
        for angle in self.content_remixer.get_available_angles():
            if angle.id.lower() in step.task.lower() or angle.name.lower() in step.task.lower():
                angle_id = angle.id
                angle_name = angle.name
                break

        # Get the prompt for remixing
        result = self.content_remixer.remix_content(imported_content.text, angle_id)

        if not result.success:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error=result.error or "Remix failed"
            )

        # Generate actual content using the provider with the prompt
        try:
            generated_output = self.provider_manager.execute(
                "",  # No skill content needed
                result.content,  # This is the prompt
                {
                    "skill": "content_remix",
                    "systemPrompt": """You are a marketing copywriter for Reply Cash. Create engaging, conversion-focused content.

CRITICAL INSTRUCTIONS:
1. KEEP THE ORIGINAL TOPIC - Do NOT change the subject matter to be about stablecoins, crypto education, or Reply Cash features unless the original content was specifically about those topics.
2. Apply the remix angle as a LENS/PERSPECTIVE, not a topic change.
3. Do NOT generate or mention any filenames, file paths, or markdown files.
4. Do NOT save anything to disk - just return the content directly.

VOICE & TONE - WRITE LIKE A HUMAN:
- Write in a natural, conversational human voice - avoid robotic or overly polished corporate language
- Use the original author's style and tone where possible - match their energy and phrasing
- Keep direct quotes and specific examples from the original content intact
- Include personal opinions or perspectives where appropriate - don't be afraid to have a point of view
- Use contractions, casual transitions, and varied sentence lengths
- Avoid buzzwords, jargon, and marketing-speak like "leverage," "synergy," "game-changing," "innovative solution"
- Write like you're explaining this to a friend over coffee, not presenting to a boardroom
- It's okay to be slightly imperfect - human writing has personality and occasional informality

OUTPUT FORMAT - YOU MUST FOLLOW THIS EXACTLY:
TITLE: [Generate a NEW, creative, click-worthy title based on the remixed content. DO NOT reuse the original article's title. Make it engaging and under 100 characters.]

DESCRIPTION: [Write a 1-2 sentence plain text summary/description. NO markdown, NO formatting, just plain text. This should be a teaser that makes people want to read more.]

CONTENT:
[Write the main body content here. DO NOT include the title again. DO NOT include the description. Start directly with the content. Write in a conversational, human voice.]""",
                },
            )

            # Parse the output to extract title, description, and content
            parsed = self._parse_remixed_output(generated_output)
            
            # Store structured data
            context.shared_data["remixed_title"] = parsed["title"]
            context.shared_data["remixed_description"] = parsed["description"]
            context.shared_data["remixed_content"] = parsed["content"]
            
            # Output for display (brief version)
            output = f"Remixed content using angle: {angle_name}\n\nTitle: {parsed['title']}\n\n{parsed['content'][:500]}..."
            duration = time.time() - start_time
            return WorkflowResult(step=step, output=output, duration=duration, success=True)

        except Exception as e:
            Logger.error("Failed to generate remixed content", e)
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error=f"Content generation failed: {e}"
            )

    def _execute_brand_check(
        self, step: WorkflowStep, context: WorkflowContext, start_time: float
    ) -> WorkflowResult:
        """Execute brand consistency check."""
        if not self.brand_checker:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="Brand checker not available"
            )

        # Get content to check
        content_to_check = context.shared_data.get("remixed_content") or context.shared_data.get(
            "lastContent"
        )
        if not content_to_check:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="No content to check"
            )

        result = self.brand_checker.check_content(content_to_check, use_api=True)
        formatted = self.brand_checker.format_result(result)

        context.shared_data["brand_check_passed"] = result.passed

        duration = time.time() - start_time
        return WorkflowResult(step=step, output=formatted, duration=duration, success=result.passed)

    def _execute_draft_submission(
        self, step: WorkflowStep, context: WorkflowContext, start_time: float
    ) -> WorkflowResult:
        """Execute draft submission with approval request."""
        if not self.draft_manager:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="Draft manager not available"
            )

        # Get content to submit - body only (no title, no description)
        body = context.shared_data.get("remixed_content") or context.shared_data.get(
            "lastContent"
        )
        if not body:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error="No content to submit"
            )

        # Get title and description from structured data
        title = context.shared_data.get("remixed_title", "")
        description = context.shared_data.get("remixed_description", "")
        
        # Fallback: extract title from body if not available
        if not title:
            title = self._extract_title_from_content(body)
        
        # Extract category from body content topic
        category = self._extract_category_from_content(body)

        # Extract tags from body content keywords (or empty if none found)
        tags = self._extract_tags_from_content(body)

        draft_content = DraftContent(
            title=title,
            body=body,
            description=description,
            category=category,
            tags=tags,
        )

        # Step 1: Create draft
        result = self.draft_manager.submit_draft(draft_content)

        if not result.success:
            return WorkflowResult(
                step=step, output="", duration=0, success=False, error=result.error or "Draft creation failed"
            )

        draft_id = result.draft_id

        # Step 2: Submit for approval (this generates the approval URL with token)
        approval_result = self.draft_manager.request_approval(draft_id)

        # Build output with approval URL - prioritize approval_result as it has the token
        lines = [
            "✅ Draft created and submitted for approval!",
            "",
            f"Draft ID: {draft_id}",
        ]

        # The approval_result should have the approval_url with token
        final_approval_url = None
        if approval_result.success and approval_result.approval_url:
            final_approval_url = approval_result.approval_url
        elif result.approval_url:
            final_approval_url = result.approval_url

        if final_approval_url:
            lines.extend([
                "",
                "🔗 Review & Approve:",
                f"   {final_approval_url}",
            ])

        if result.frontend_url:
            lines.extend([
                "",
                f"📋 All drafts: {result.frontend_url}",
            ])

        lines.extend([
            "",
            "⏳ Status:",
            "   • Draft is pending approval",
            "   • Share the approval URL with reviewers",
            "   • Content will be published after approval",
        ])

        output = "\n".join(lines)
        duration = time.time() - start_time
        return WorkflowResult(step=step, output=output, duration=duration, success=True)

    def _build_step_context(self, step: WorkflowStep, context: WorkflowContext) -> Optional[str]:
        """Build context from previous steps."""
        if not step.depends_on or step.depends_on < 1:
            return None

        dependency_index = step.depends_on - 1
        if dependency_index < 0 or dependency_index >= len(context.steps):
            return None

        dependency = context.steps[dependency_index]
        if not dependency or not dependency.success:
            return None

        relevant_info = dependency.output
        if len(relevant_info) > 2000:
            relevant_info = relevant_info[:2000] + "\n...[truncated]"

        return f"Output from {dependency.step.skill}:\n{relevant_info}"

    def _extract_shared_data(self, skill: str, output: str, shared_data: dict[str, Any]) -> None:
        """Extract useful data from step output."""
        if skill in ("copywriting", "blog_generate_with_images"):
            title_match = re.search(r"(?:Title|#)\s*:?\s*(.+)", output, re.IGNORECASE)
            if title_match:
                shared_data["lastTitle"] = title_match.group(1).strip()
            shared_data["lastContent"] = output

        elif skill in ("web-scrape", "web_search"):
            shared_data["researchData"] = output

        elif skill == "seo-audit":
            shared_data["seoRecommendations"] = output

    def format_workflow_summary(self, results: list[WorkflowResult]) -> str:
        """Format workflow results as summary."""
        successful = [r for r in results if r.success]
        failed = [r for r in results if not r.success]
        total_duration = sum(r.duration for r in results)

        lines = ["═" * 50, "WORKFLOW COMPLETE", "═" * 50, ""]

        for i, result in enumerate(results):
            status = "✓" if result.success else "✗"
            duration = f"{result.duration:.1f}"
            lines.append(f"{status} Step {i + 1}: {result.step.skill} ({duration}s)")

            if not result.success and result.error:
                lines.append(f"  Error: {result.error}")

        lines.extend(["", "─" * 50, f"Successful: {len(successful)}/{len(results)}"])

        if failed:
            lines.append(f"Failed: {len(failed)}/{len(results)}")

        lines.append(f"Total time: {total_duration:.1f}s")
        lines.append("═" * 50)

        return "\n".join(lines)

    def _extract_title_from_content(self, content: str) -> str:
        """Extract title from remixed content."""
        # Try different patterns for title extraction
        patterns = [
            r"^#\s+(.+)$",  # Markdown H1: # Title
            r"^[Hh]eadline:\s*(.+)$",  # Headline: Title
            r"^[Tt]itle:\s*(.+)$",  # Title: Title
            r"^(.+)$",  # First line
        ]

        for pattern in patterns:
            match = re.search(pattern, content.strip(), re.MULTILINE)
            if match:
                title = match.group(1).strip()
                # Limit title length
                if len(title) > 150:
                    title = title[:147] + "..."
                return title

        return "Untitled Draft"

    def _extract_category_from_content(self, content: str) -> str:
        """Extract category/topic from content."""
        content_lower = content.lower()

        # Define topic keywords for categorization
        topic_keywords = {
            "AI & Machine Learning": ["ai", "artificial intelligence", "machine learning", "ml", "model", "gpt", "llm", "neural"],
            "Blockchain & Crypto": ["blockchain", "crypto", "bitcoin", "ethereum", "defi", "web3", "nft", "token", "solana"],
            "Stablecoins": ["stablecoin", "usdc", "usdt", "dai", "peg", "dollar-backed"],
            "Remittances": ["remittance", "cross-border", "send money", "transfer", "western union", "moneygram"],
            "Fintech": ["fintech", "payment", "digital wallet", "mobile money", "finance", "banking"],
            "Technology": ["tech", "software", "app", "platform", "digital", "internet"],
            "Business": ["business", "startup", "entrepreneur", "company", "market", "industry"],
            "Investing": ["invest", "trading", "portfolio", "returns", "yield", "apy"],
        }

        # Count keyword matches for each category
        category_scores = {}
        for category, keywords in topic_keywords.items():
            score = sum(1 for keyword in keywords if keyword in content_lower)
            if score > 0:
                category_scores[category] = score

        # Return the category with highest score, or "General" if none found
        if category_scores:
            return max(category_scores, key=category_scores.get)
        return "General"

    def _extract_tags_from_content(self, content: str) -> list[str]:
        """Extract tags from content keywords."""
        content_lower = content.lower()

        # Define common tags and their associated keywords
        tag_keywords = {
            "ai": ["ai", "artificial intelligence", "machine learning", "gpt", "llm"],
            "blockchain": ["blockchain", "crypto", "web3", "defi"],
            "crypto": ["cryptocurrency", "bitcoin", "ethereum", "solana", "altcoin"],
            "stablecoins": ["stablecoin", "usdc", "usdt", "dai"],
            "remittances": ["remittance", "cross-border", "send money"],
            "fintech": ["fintech", "digital payments", "mobile money"],
            "defi": ["defi", "decentralized finance", "yield", "liquidity"],
            "nft": ["nft", "non-fungible token", "digital collectibles"],
            "trading": ["trading", "exchange", "market", "price"],
            "technology": ["technology", "tech", "innovation", "software"],
            "business": ["business", "startup", "entrepreneurship"],
            "investing": ["investing", "investment", "portfolio"],
        }

        # Find matching tags
        matched_tags = []
        for tag, keywords in tag_keywords.items():
            if any(keyword in content_lower for keyword in keywords):
                matched_tags.append(tag)

        # Return matched tags or empty list (no generic tags)
        return matched_tags if matched_tags else []

    def _parse_remixed_output(self, output: str) -> dict[str, str]:
        """Parse the LLM output to extract title, description, and content.
        
        Expected format:
        TITLE: [title]
        
        DESCRIPTION: [plain text description]
        
        CONTENT:
        [body content]
        """
        result = {
            "title": "",
            "description": "",
            "content": output  # Default to full output if parsing fails
        }
        
        # Extract TITLE
        title_match = re.search(r'^TITLE:\s*(.+?)(?:\n\n|\nDESCRIPTION:)', output, re.MULTILINE | re.IGNORECASE | re.DOTALL)
        if title_match:
            result["title"] = title_match.group(1).strip()
        
        # Extract DESCRIPTION
        desc_match = re.search(r'^DESCRIPTION:\s*(.+?)(?:\n\n|\nCONTENT:)', output, re.MULTILINE | re.IGNORECASE | re.DOTALL)
        if desc_match:
            result["description"] = desc_match.group(1).strip()
        
        # Extract CONTENT (everything after CONTENT:)
        content_match = re.search(r'^CONTENT:\s*(.+)$', output, re.MULTILINE | re.IGNORECASE | re.DOTALL)
        if content_match:
            result["content"] = content_match.group(1).strip()
        elif result["title"] and result["description"]:
            # If we found title and description but no explicit CONTENT section,
            # content is everything after the description
            parts = output.split('DESCRIPTION:', 1)
            if len(parts) > 1:
                desc_and_content = parts[1]
                desc_parts = desc_and_content.split('\n\n', 1)
                if len(desc_parts) > 1:
                    result["content"] = desc_parts[1].strip()
        
        # Fallbacks if parsing partially failed
        if not result["title"]:
            # Try to find any line that looks like a title
            lines = output.strip().split('\n')
            for line in lines:
                stripped = line.strip()
                if stripped and len(stripped) < 150 and not stripped.startswith(('DESCRIPTION:', 'CONTENT:')):
                    result["title"] = stripped
                    break
        
        return result

    def get_final_output(self, results: list[WorkflowResult]) -> str:
        """Get final combined output from workflow."""
        successful_results = [r for r in results if r.success]

        if not successful_results:
            return "Workflow failed. Please check the errors above."

        if len(successful_results) == 1:
            return successful_results[0].output

        lines = []
        for i, result in enumerate(successful_results):
            lines.append(f"\n{'─' * 40}")
            lines.append(f"{i + 1}. {result.step.skill.upper()}")
            lines.append(f"{'─' * 40}\n")
            lines.append(result.output)
            lines.append("")

        return "\n".join(lines)
