"""
Enhanced OSINT Agent - Advanced dark web reconnaissance with browser automation

Extends the base OSINT agent with:
- LLM-controlled browser automation via Tor
- Multi-agent hierarchy for parallel investigations
- Secrets management for credentials
- Evidence capture and archival
- Extension hooks for customization
"""

import asyncio
import time
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Callable, Optional

from groknroll.agents.base_agent import AgentCapability, AgentConfig, AgentResponse
from groknroll.agents.hierarchy import (
    AgentProfile,
    AgentRole,
    DEFAULT_PROFILES,
    HierarchicalAgent,
    InterventionException,
)
from groknroll.agents.osint_agent import InvestigationResult, OsintAgent
from groknroll.core.extensions import HookPoint, execute_hook, get_extension_manager
from groknroll.core.secrets import SecretsManager, get_secrets_manager
from groknroll.osint.artifact_extractor import Artifact
from groknroll.osint.content_scraper import ScrapedContent
from groknroll.osint.dark_web_search import SearchResult
from groknroll.osint.tor_manager import TorManager

# Optional browser automation
try:
    from groknroll.osint.browser_automation import (
        BrowserAction,
        BrowserAgentTask,
        BrowserResult,
        BrowserState,
        TorBrowserAutomation,
    )

    BROWSER_AVAILABLE = True
except ImportError:
    BROWSER_AVAILABLE = False


# OSINT-specific agent profiles
OSINT_PROFILES: dict[str, AgentProfile] = {
    "osint_lead": AgentProfile(
        role=AgentRole.ANALYST,
        name="OSINT Lead",
        system_prompt="""You are a senior OSINT analyst leading dark web investigations.
Coordinate subordinate agents, prioritize targets, and synthesize intelligence.
Focus on identifying threats, actors, and actionable intelligence.""",
        capabilities=[
            AgentCapability.READ_FILES,
            AgentCapability.WRITE_FILES,
            AgentCapability.SEARCH_CODE,
            AgentCapability.ANALYZE_CODE,
        ],
        model="gpt-4o",
        max_subordinates=5,
        can_spawn_subordinates=True,
    ),
    "search_agent": AgentProfile(
        role=AgentRole.RESEARCHER,
        name="Search Specialist",
        system_prompt="""You are a search specialist for dark web reconnaissance.
Identify relevant search terms, explore search engines, and find targets.
Report findings to your superior agent.""",
        capabilities=[
            AgentCapability.READ_FILES,
            AgentCapability.SEARCH_CODE,
        ],
        model="gpt-4o-mini",
        can_spawn_subordinates=False,
    ),
    "scraper_agent": AgentProfile(
        role=AgentRole.RESEARCHER,
        name="Content Scraper",
        system_prompt="""You are a content extraction specialist.
Navigate dark web sites, extract relevant content, and capture evidence.
Use browser automation for interactive sites when needed.""",
        capabilities=[
            AgentCapability.READ_FILES,
            AgentCapability.WRITE_FILES,
        ],
        model="gpt-4o-mini",
        can_spawn_subordinates=False,
    ),
    "analyst_agent": AgentProfile(
        role=AgentRole.ANALYST,
        name="Intelligence Analyst",
        system_prompt="""You are an intelligence analyst for OSINT operations.
Analyze extracted content, identify patterns, and assess threats.
Provide actionable intelligence recommendations.""",
        capabilities=[
            AgentCapability.READ_FILES,
            AgentCapability.ANALYZE_CODE,
        ],
        model="gpt-4o",
        can_spawn_subordinates=False,
    ),
}


@dataclass
class BrowserEvidence:
    """Evidence captured via browser automation."""

    url: str
    title: str
    screenshot_path: Optional[str] = None
    content_path: Optional[str] = None
    artifacts: list[Artifact] = field(default_factory=list)
    timestamp: datetime = field(default_factory=datetime.utcnow)
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class EnhancedInvestigationResult(InvestigationResult):
    """Extended investigation result with browser evidence."""

    browser_evidence: list[BrowserEvidence] = field(default_factory=list)
    subordinate_reports: list[dict[str, Any]] = field(default_factory=list)
    intervention_occurred: bool = False
    total_agents_used: int = 1


class EnhancedOsintAgent(HierarchicalAgent):
    """
    Enhanced OSINT Agent with browser automation and multi-agent support.

    Extends HierarchicalAgent to provide:
    - LLM-controlled Tor browser for interactive sites
    - Multi-agent hierarchy for parallel investigations
    - Secrets management for credentials
    - Evidence capture and archival
    - Extension hooks for customization

    Usage:
        agent = EnhancedOsintAgent(project_path=Path("."))

        # Simple investigation
        result = await agent.investigate("ransomware lockbit")

        # Deep investigation with browser
        result = await agent.deep_investigate(
            "threat actor APT29",
            use_browser=True,
            parallel=True
        )

    Requires:
        - Tor daemon on localhost:9050
        - playwright (for browser automation)
    """

    def __init__(
        self,
        project_path: Path,
        backend: str = "anthropic",
        model: str = "claude-sonnet-4-20250514",
        tor_host: str = "127.0.0.1",
        tor_port: int = 9050,
        max_workers: int = 5,
        output_dir: Optional[Path] = None,
        secrets_manager: Optional[SecretsManager] = None,
    ):
        """
        Initialize Enhanced OSINT Agent.

        Args:
            project_path: Project root path
            backend: LLM backend
            model: LLM model identifier
            tor_host: Tor proxy host
            tor_port: Tor proxy port
            max_workers: Concurrent workers
            output_dir: Report output directory
            secrets_manager: Optional secrets manager
        """
        # Create agent config
        config = AgentConfig(
            name="enhanced_osint",
            description="Enhanced OSINT agent with browser automation",
            capabilities=[
                AgentCapability.READ_FILES,
                AgentCapability.WRITE_FILES,
                AgentCapability.SEARCH_CODE,
                AgentCapability.ANALYZE_CODE,
            ],
            model=model,
            max_cost=20.0,
            timeout=1200,
        )

        # Initialize hierarchical agent
        super().__init__(
            config=config,
            project_path=project_path,
            profile=OSINT_PROFILES["osint_lead"],
            agent_number=0,
        )

        # Store configuration
        self.backend = backend
        self.tor_host = tor_host
        self.tor_port = tor_port
        self.max_workers = max_workers
        self.output_dir = output_dir or project_path / "osint_reports"
        self.output_dir.mkdir(parents=True, exist_ok=True)

        # Initialize components
        self._base_osint = OsintAgent(
            project_path=project_path,
            backend=backend,
            model=model,
            tor_host=tor_host,
            tor_port=tor_port,
            max_workers=max_workers,
            output_dir=self.output_dir,
        )

        self._secrets = secrets_manager or get_secrets_manager(project_path)
        self._browser: Optional[TorBrowserAutomation] = None
        self._browser_task: Optional[BrowserAgentTask] = None

        # Investigation state
        self._current_investigation: Optional[str] = None
        self._evidence: list[BrowserEvidence] = []

    @property
    def tor_manager(self) -> TorManager:
        """Get TorManager from base agent."""
        return self._base_osint.tor_manager

    @property
    def browser_available(self) -> bool:
        """Check if browser automation is available."""
        return BROWSER_AVAILABLE

    async def start_browser(self) -> bool:
        """
        Start the Tor browser.

        Returns:
            True if started successfully
        """
        if not BROWSER_AVAILABLE:
            return False

        if self._browser is not None:
            return True

        try:
            self._browser = TorBrowserAutomation(
                tor_manager=self.tor_manager,
                headless=True,
                user_data_dir=str(self.output_dir / "browser_data"),
            )
            await self._browser.start()
            self._browser_task = BrowserAgentTask(self.tor_manager)
            return True
        except Exception as e:
            self._browser = None
            return False

    async def stop_browser(self) -> None:
        """Stop the Tor browser."""
        if self._browser:
            await self._browser.close()
            self._browser = None
            self._browser_task = None

    def execute(self, task: str, context: Optional[dict[str, Any]] = None) -> AgentResponse:
        """
        Execute OSINT task synchronously.

        Args:
            task: Investigation query
            context: Additional options

        Returns:
            AgentResponse
        """
        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                import concurrent.futures

                with concurrent.futures.ThreadPoolExecutor() as pool:
                    future = pool.submit(
                        asyncio.run,
                        self.execute_async(task, context),
                    )
                    return future.result()
            else:
                return loop.run_until_complete(self.execute_async(task, context))
        except RuntimeError:
            return asyncio.run(self.execute_async(task, context))

    async def execute_async(
        self,
        task: str,
        context: Optional[dict[str, Any]] = None,
    ) -> AgentResponse:
        """
        Execute OSINT task asynchronously.

        Args:
            task: Investigation query
            context: Additional options (deep, use_browser, parallel)

        Returns:
            AgentResponse
        """
        start_time = time.time()
        context = context or {}

        # Execute pre-hook
        hook_ctx = await execute_hook(
            HookPoint.MONOLOGUE_START,
            self,
            {"task": task, "context": context},
        )

        if hook_ctx.cancelled:
            return AgentResponse(
                success=False,
                message="Investigation cancelled",
                agent_name=self.agent_name,
                task=task,
            )

        try:
            # Determine investigation type
            deep = context.get("deep", False)
            use_browser = context.get("use_browser", False)
            parallel = context.get("parallel", False)

            if deep:
                result = await self.deep_investigate(
                    task,
                    use_browser=use_browser,
                    parallel=parallel,
                )
            else:
                result = await self.investigate(task)

            elapsed = time.time() - start_time

            # Execute post-hook
            await execute_hook(
                HookPoint.MONOLOGUE_END,
                self,
                {"task": task, "result": result},
            )

            response = AgentResponse(
                success=result.status == "complete",
                message=self._format_result(result),
                agent_name=self.agent_name,
                task=task,
                time=elapsed,
                metadata={
                    "results_count": result.results_count,
                    "scraped_count": result.scraped_count,
                    "artifacts_count": result.artifacts_count,
                    "report_path": str(result.report_path) if result.report_path else None,
                },
            )

            self._log_execution(response)
            return response

        except InterventionException as e:
            return AgentResponse(
                success=False,
                message=f"Intervention: {str(e)}",
                agent_name=self.agent_name,
                task=task,
                time=time.time() - start_time,
            )

        except Exception as e:
            await execute_hook(
                HookPoint.ERROR_OCCURRED,
                self,
                {"error": e, "task": task},
            )

            return AgentResponse(
                success=False,
                message=f"Investigation failed: {str(e)}",
                agent_name=self.agent_name,
                task=task,
                time=time.time() - start_time,
            )

    async def investigate(self, query: str) -> InvestigationResult:
        """
        Run basic OSINT investigation.

        Args:
            query: Search query

        Returns:
            InvestigationResult
        """
        # Mask any secrets in query
        safe_query = self._secrets.mask(query)

        # Run base investigation
        return self._base_osint.investigate(safe_query, deep=False)

    async def deep_investigate(
        self,
        query: str,
        use_browser: bool = True,
        parallel: bool = True,
        max_browser_pages: int = 10,
    ) -> EnhancedInvestigationResult:
        """
        Run deep OSINT investigation with optional browser automation.

        Args:
            query: Search query
            use_browser: Use browser for interactive sites
            parallel: Use subordinate agents for parallel work
            max_browser_pages: Maximum pages to visit with browser

        Returns:
            EnhancedInvestigationResult
        """
        start_time = time.time()
        self._current_investigation = query
        self._evidence = []
        subordinate_reports = []
        total_agents = 1

        # Check Tor connectivity
        if not self.tor_manager.check_connection():
            return EnhancedInvestigationResult(
                query=query,
                refined_query=query,
                search_results=[],
                scraped_contents=[],
                artifacts=[],
                report=None,
                report_path=None,
                duration=time.time() - start_time,
                status="failed",
                error="Tor proxy not available",
                browser_evidence=[],
                subordinate_reports=[],
                total_agents_used=1,
            )

        # Phase 1: Basic investigation
        base_result = self._base_osint.investigate(query, deep=True, save_report=False)

        # Phase 2: Browser automation for top results
        browser_evidence = []
        if use_browser and BROWSER_AVAILABLE and base_result.search_results:
            try:
                await self.start_browser()

                # Select top results for browser investigation
                top_urls = [r.url for r in base_result.search_results[:max_browser_pages]]
                browser_evidence = await self._browser_investigate(top_urls)

            except Exception as e:
                # Browser failed, continue without it
                pass
            finally:
                await self.stop_browser()

        # Phase 3: Parallel subordinate investigation (if enabled)
        if parallel and len(base_result.search_results) > 20:
            try:
                # Spawn analysis subordinate
                analyst = await self.spawn_subordinate(
                    f"Analyze OSINT findings for: {query}",
                    profile=OSINT_PROFILES["analyst_agent"],
                )
                total_agents += 1

                # Delegate analysis
                analysis_task = await self.delegate_task(
                    f"Analyze the following artifacts and provide threat assessment:\n"
                    f"Query: {query}\n"
                    f"Artifacts found: {len(base_result.artifacts)}\n"
                    f"Key findings: {base_result.search_results[:5]}",
                    role=AgentRole.ANALYST,
                    wait=True,
                )

                if analysis_task.result:
                    subordinate_reports.append({
                        "agent": analyst.agent_name,
                        "task": analysis_task.description,
                        "status": analysis_task.status,
                        "result": analysis_task.result.message,
                    })

            except Exception as e:
                # Subordinate failed, continue
                pass

        # Combine all artifacts
        all_artifacts = list(base_result.artifacts)
        for evidence in browser_evidence:
            all_artifacts.extend(evidence.artifacts)

        # Generate final report
        report_path = None
        if self._base_osint.reporter:
            markdown = self._base_osint.reporter.generate(
                query=query,
                refined_query=base_result.refined_query,
                results=base_result.search_results,
                scraped_contents=base_result.scraped_contents,
                artifacts=all_artifacts,
                report=base_result.report,
            )

            # Add browser evidence section
            if browser_evidence:
                markdown += "\n\n## Browser Evidence\n\n"
                for evidence in browser_evidence:
                    markdown += f"### {evidence.title}\n"
                    markdown += f"- URL: {evidence.url}\n"
                    markdown += f"- Timestamp: {evidence.timestamp.isoformat()}\n"
                    if evidence.screenshot_path:
                        markdown += f"- Screenshot: {evidence.screenshot_path}\n"
                    if evidence.artifacts:
                        markdown += f"- Artifacts: {len(evidence.artifacts)}\n"
                    markdown += "\n"

            # Add subordinate reports
            if subordinate_reports:
                markdown += "\n\n## Subordinate Agent Reports\n\n"
                for report in subordinate_reports:
                    markdown += f"### {report['agent']}\n"
                    markdown += f"**Task:** {report['task']}\n\n"
                    markdown += f"**Status:** {report['status']}\n\n"
                    markdown += f"**Result:**\n{report['result']}\n\n"

            report_path = self._base_osint.reporter.save(markdown)

        return EnhancedInvestigationResult(
            query=query,
            refined_query=base_result.refined_query,
            search_results=base_result.search_results,
            scraped_contents=base_result.scraped_contents,
            artifacts=all_artifacts,
            report=base_result.report,
            report_path=report_path,
            duration=time.time() - start_time,
            status="complete",
            browser_evidence=browser_evidence,
            subordinate_reports=subordinate_reports,
            total_agents_used=total_agents,
        )

    async def _browser_investigate(
        self,
        urls: list[str],
    ) -> list[BrowserEvidence]:
        """
        Investigate URLs using browser automation.

        Args:
            urls: URLs to visit

        Returns:
            List of BrowserEvidence
        """
        evidence_list = []

        if not self._browser:
            return evidence_list

        evidence_dir = self.output_dir / "evidence" / datetime.utcnow().strftime("%Y%m%d_%H%M%S")
        evidence_dir.mkdir(parents=True, exist_ok=True)

        for i, url in enumerate(urls):
            try:
                # Navigate to URL
                state = await self._browser.navigate(url)

                # Take screenshot
                screenshot_b64 = await self._browser.screenshot(full_page=True)
                screenshot_path = evidence_dir / f"page_{i:03d}.png"

                import base64

                with open(screenshot_path, "wb") as f:
                    f.write(base64.b64decode(screenshot_b64))

                # Save content
                content = state.page_content or ""
                content_path = evidence_dir / f"page_{i:03d}.txt"
                with open(content_path, "w", encoding="utf-8") as f:
                    f.write(f"URL: {state.url}\n")
                    f.write(f"Title: {state.title}\n")
                    f.write("-" * 50 + "\n")
                    f.write(content)

                # Extract artifacts from browser content
                artifacts = self._base_osint.extractor.extract_all(content, url)

                evidence = BrowserEvidence(
                    url=state.url,
                    title=state.title,
                    screenshot_path=str(screenshot_path),
                    content_path=str(content_path),
                    artifacts=artifacts,
                    metadata={"index": i},
                )

                evidence_list.append(evidence)
                self._evidence.append(evidence)

            except Exception as e:
                # Log error but continue
                continue

        return evidence_list

    async def browse_url(
        self,
        url: str,
        actions: Optional[list[BrowserAction]] = None,
    ) -> BrowserResult:
        """
        Browse a specific URL with optional actions.

        Args:
            url: URL to visit
            actions: Optional actions to perform

        Returns:
            BrowserResult
        """
        if not BROWSER_AVAILABLE:
            return BrowserResult(
                success=False,
                message="Browser automation not available",
                error="playwright not installed",
            )

        try:
            await self.start_browser()

            # Navigate
            state = await self._browser.navigate(url)

            # Execute actions if provided
            if actions:
                result = await self._browser.execute_actions(actions)
                return result

            # Take screenshot
            screenshot = await self._browser.screenshot()

            return BrowserResult(
                success=True,
                message=f"Visited: {state.title}",
                screenshots=[screenshot],
                extracted_content=state.page_content,
                final_url=state.url,
            )

        except Exception as e:
            return BrowserResult(
                success=False,
                message=f"Browser failed: {str(e)}",
                error=str(e),
            )

    def check_status(self) -> dict[str, Any]:
        """Get agent status."""
        base_status = self._base_osint.check_status()

        return {
            **base_status,
            "agent_id": self.agent_id,
            "agent_name": self.agent_name,
            "browser_available": BROWSER_AVAILABLE,
            "browser_active": self._browser is not None,
            "subordinates": len(self._subordinates),
            "evidence_captured": len(self._evidence),
            "current_investigation": self._current_investigation,
        }

    def _format_result(self, result: InvestigationResult) -> str:
        """Format investigation result."""
        lines = [
            "🔍 OSINT Investigation Complete",
            f"Query: {result.query}",
            f"Results: {result.results_count} found, {result.scraped_count} scraped",
            f"Artifacts: {result.artifacts_count} extracted",
            f"Duration: {result.duration:.1f}s",
        ]

        if isinstance(result, EnhancedInvestigationResult):
            lines.append(f"Browser Evidence: {len(result.browser_evidence)} pages")
            lines.append(f"Agents Used: {result.total_agents_used}")

        if result.report_path:
            lines.append(f"Report: {result.report_path}")

        return "\n".join(lines)

    async def cleanup(self) -> None:
        """Clean up resources."""
        await self.stop_browser()
        await super().cleanup()
