"""Secure Release Pipeline

A comprehensive security pipeline that composes multiple security workflows
for maximum coverage before release approval.

Orchestrates:
1. SecurityAuditCrew (optional, parallel) - 5-agent multi-agent security crew
2. SecurityAuditWorkflow - OWASP-focused vulnerability scanning
3. CodeReviewWorkflow - Security-aware code review (optional)
4. ReleasePreparationWorkflow - Pre-release quality gate

Copyright 2025 Smart-AI-Memory
Licensed under the Apache License, Version 2.0
"""

import asyncio
import logging
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any

from .base import BaseWorkflow, WorkflowResult
from .compat import ModelTier

logger = logging.getLogger(__name__)


@dataclass
class SecureReleaseResult:
    """Result from SecureReleasePipeline execution."""

    success: bool
    go_no_go: str  # "GO", "NO_GO", "CONDITIONAL"

    # Individual workflow results
    crew_report: dict | None = None
    security_audit: WorkflowResult | None = None
    code_review: WorkflowResult | None = None
    release_prep: WorkflowResult | None = None

    # Unified metrics
    combined_risk_score: float = 0.0
    total_findings: int = 0
    critical_count: int = 0
    high_count: int = 0

    # Cost tracking
    total_cost: float = 0.0
    total_duration_ms: int = 0

    # Recommendations
    blockers: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)
    recommendations: list[str] = field(default_factory=list)

    # Metadata
    mode: str = "full"
    crew_enabled: bool = False

    def to_dict(self) -> dict:
        """Convert result to dictionary."""
        return {
            "success": self.success,
            "go_no_go": self.go_no_go,
            "combined_risk_score": self.combined_risk_score,
            "total_findings": self.total_findings,
            "critical_count": self.critical_count,
            "high_count": self.high_count,
            "total_cost": self.total_cost,
            "total_duration_ms": self.total_duration_ms,
            "blockers": self.blockers,
            "warnings": self.warnings,
            "recommendations": self.recommendations,
            "mode": self.mode,
            "crew_enabled": self.crew_enabled,
        }

    @property
    def formatted_report(self) -> str:
        """Generate human-readable report."""
        return format_secure_release_report(self)


class SecureReleasePipeline(BaseWorkflow):
    """Comprehensive security pipeline for release preparation.

    This pipeline composes multiple security workflows to provide
    maximum coverage before release approval.

    Execution modes:
    - "full": Run all workflows (SecurityAuditCrew + all workflows) [DEFAULT]
    - "standard": Skip crew, run all workflows (fallback when crew unavailable)

    Note: For quick release checks without full security audit, use the
    ReleasePreparationWorkflow directly instead.

    Usage:
        pipeline = SecureReleasePipeline(mode="full")
        result = await pipeline.execute(
            path="./src",
            diff="...",
            files_changed=[...]
        )

        if result.go_no_go == "GO":
            print("Ready for release!")
        else:
            for blocker in result.blockers:
                print(f"BLOCKER: {blocker}")
    """

    name = "secure-release"
    description = "Comprehensive security pipeline composing multiple workflows"

    async def run_stage(self, stage_name: str, tier: ModelTier, input_data: Any) -> Any:
        """Not used — this workflow overrides execute() directly."""
        raise NotImplementedError("SecureReleasePipeline uses execute(), not run_stage()")

    def __init__(
        self,
        mode: str = "full",  # "full" or "standard"
        use_crew: bool | None = None,  # Override mode's crew setting
        parallel_crew: bool = True,  # Run crew in parallel with first workflow
        crew_config: dict | None = None,
        **kwargs: Any,
    ):
        """Initialize secure release pipeline.

        Args:
            mode: Execution mode - "full" (with crew, DEFAULT) or "standard" (skip crew)
            use_crew: Override crew setting (None uses mode default: full=True, standard=False)
            parallel_crew: Run SecurityAuditCrew in parallel with first workflow
            crew_config: Configuration for SecurityAuditCrew
            **kwargs: Additional arguments passed to child workflows

        """
        super().__init__()

        # Validate mode
        if mode not in ("full", "standard"):
            raise ValueError(f"Invalid mode '{mode}'. Must be 'full' or 'standard'.")

        self.mode = mode
        self.use_crew = use_crew if use_crew is not None else (mode == "full")
        self.parallel_crew = parallel_crew
        self.crew_config = crew_config or {}
        self.kwargs = kwargs

    async def execute(
        self,
        path: str = ".",
        diff: str = "",
        files_changed: list[str] | None = None,
        since: str = "1 week ago",
        **kwargs: Any,
    ) -> SecureReleaseResult:
        """Execute the secure release pipeline.

        Args:
            path: Path to codebase to analyze
            diff: Git diff for code review (optional)
            files_changed: List of changed files (optional)
            since: Period for changelog generation
            **kwargs: Additional arguments

        Returns:
            SecureReleaseResult with combined analysis

        """
        try:
            from .security_adapters import (
                _check_crew_available,
                _get_crew_audit,
                crew_report_to_workflow_format,
            )

            adapters_available = True
        except ImportError:
            adapters_available = False

            def _check_crew_available():
                return False

            _get_crew_audit = None
            crew_report_to_workflow_format = None

        started_at = datetime.now()

        crew_report = None
        security_result = None
        code_review_result = None
        release_result = None

        total_cost = 0.0
        blockers: list[str] = []
        warnings: list[str] = []
        recommendations: list[str] = []

        try:
            # Step 1: SecurityAuditCrew (parallel or first)
            crew_task = None
            crew_enabled = self.use_crew and adapters_available and _check_crew_available()

            if crew_enabled:
                if self.parallel_crew:
                    # Start crew in parallel
                    crew_task = asyncio.create_task(_get_crew_audit(path, self.crew_config))
                else:
                    # Run crew first, then proceed
                    crew_report_obj = await _get_crew_audit(path, self.crew_config)
                    if crew_report_obj:
                        crew_report = crew_report_to_workflow_format(crew_report_obj)

            # Step 2: SecurityAuditWorkflow
            from .security_audit import SecurityAuditWorkflow

            security_workflow = SecurityAuditWorkflow(**self.kwargs)
            security_result = await security_workflow.execute(path=path)
            total_cost += security_result.cost_report.total_cost

            # Collect crew results if running in parallel
            if crew_task:
                try:
                    crew_report_obj = await asyncio.wait_for(crew_task, timeout=300.0)
                    if crew_report_obj:
                        crew_report = crew_report_to_workflow_format(crew_report_obj)
                except asyncio.TimeoutError:
                    logger.warning("SecurityAuditCrew timed out")
                    warnings.append("SecurityAuditCrew timed out - results not included")

            # Step 3: CodeReviewWorkflow (if diff provided)
            if diff:
                from .code_review import CodeReviewWorkflow

                code_workflow = CodeReviewWorkflow(**self.kwargs)

                # Pass crew findings as external audit if available
                code_input: dict = {
                    "diff": diff,
                    "files_changed": files_changed or [],
                }
                if crew_report:
                    code_input["external_audit_results"] = crew_report

                code_review_result = await code_workflow.execute(**code_input)
                total_cost += code_review_result.cost_report.total_cost

            # Step 4: ReleasePreparationWorkflow
            from .release_prep import ReleasePreparationWorkflow

            release_workflow = ReleasePreparationWorkflow(**self.kwargs)
            release_result = await release_workflow.execute(path=path, since=since)
            total_cost += release_result.cost_report.total_cost

            # Aggregate results
            combined_risk_score = self._calculate_combined_risk(
                crew_report,
                security_result,
                code_review_result,
                release_result,
            )

            findings = self._aggregate_findings(crew_report, security_result, code_review_result)

            # Determine go/no-go
            go_no_go = self._determine_go_no_go(combined_risk_score, findings, release_result)

            blockers, warnings, recommendations = self._generate_recommendations(
                crew_report,
                security_result,
                code_review_result,
                release_result,
            )

        except Exception as e:  # noqa: BLE001
            logger.error(f"Secure release pipeline failed: {e}")
            blockers.append(f"Pipeline failed: {e!s}")
            go_no_go = "NO_GO"
            combined_risk_score = 100.0
            findings = {"critical": 0, "high": 0, "total": 0}

        completed_at = datetime.now()
        duration_ms = int((completed_at - started_at).total_seconds() * 1000)

        return SecureReleaseResult(
            success=go_no_go != "NO_GO",
            go_no_go=go_no_go,
            crew_report=crew_report,
            security_audit=security_result,
            code_review=code_review_result,
            release_prep=release_result,
            combined_risk_score=combined_risk_score,
            total_findings=findings.get("total", 0),
            critical_count=findings.get("critical", 0),
            high_count=findings.get("high", 0),
            total_cost=total_cost,
            total_duration_ms=duration_ms,
            blockers=blockers,
            warnings=warnings,
            recommendations=recommendations,
            mode=self.mode,
            crew_enabled=crew_report is not None,
        )

    def _calculate_combined_risk(
        self,
        crew_report: dict | None,
        security_result: WorkflowResult | None,
        code_review_result: WorkflowResult | None,
        release_result: WorkflowResult | None,
    ) -> float:
        """Calculate weighted combined risk score from all sources.

        Crew results receive 1.5x weight, security audit 1.0x, and
        code review 0.8x.  The code review security_score is inverted
        to a risk value (100 - score).

        Args:
            crew_report: SecurityAuditCrew results dict.
            security_result: SecurityAuditWorkflow result.
            code_review_result: CodeReviewWorkflow result.
            release_result: ReleasePreparationWorkflow result
                (unused in scoring, reserved for future use).

        Returns:
            Combined risk score capped at 100.0.

        """
        scores = []
        weights = []

        if crew_report:
            scores.append(crew_report.get("risk_score", 0))
            weights.append(1.5)  # Crew gets higher weight

        if security_result and security_result.final_output:
            assessment = security_result.final_output.get("assessment", {})
            scores.append(assessment.get("risk_score", 0))
            weights.append(1.0)

        if code_review_result and code_review_result.final_output:
            security_score = code_review_result.final_output.get("security_score", 90)
            # Convert to risk (100 - security_score)
            scores.append(100 - security_score)
            weights.append(0.8)

        if not scores:
            return 0.0

        weighted_sum = sum(s * w for s, w in zip(scores, weights, strict=False))
        return float(min(100.0, weighted_sum / sum(weights)))

    def _aggregate_findings(
        self,
        crew_report: dict | None,
        security_result: WorkflowResult | None,
        code_review_result: WorkflowResult | None,
    ) -> dict:
        """Aggregate finding counts from all security sources.

        Takes the maximum count per severity level across sources
        to avoid double-counting overlapping findings.

        Args:
            crew_report: SecurityAuditCrew results dict.
            security_result: SecurityAuditWorkflow result.
            code_review_result: CodeReviewWorkflow result.

        Returns:
            Dict with ``critical``, ``high``, and ``total`` counts.

        """
        critical = 0
        high = 0
        total = 0

        if crew_report:
            assessment = crew_report.get("assessment", {})
            critical += len(assessment.get("critical_findings", []))
            high += len(assessment.get("high_findings", []))
            total += crew_report.get("finding_count", 0)

        if security_result and security_result.final_output:
            assessment = security_result.final_output.get("assessment", {})
            severity = assessment.get("severity_breakdown", {})
            critical = max(critical, severity.get("critical", 0))
            high = max(high, severity.get("high", 0))
            total = max(
                total,
                sum(severity.values()) if severity else 0,
            )

        if code_review_result and code_review_result.final_output:
            if code_review_result.final_output.get("has_critical_issues"):
                critical = max(critical, 1)

        return {"critical": critical, "high": high, "total": total}

    def _determine_go_no_go(
        self,
        risk_score: float,
        findings: dict,
        release_result: WorkflowResult | None,
    ) -> str:
        """Determine release go/no-go decision.

        Decision logic:
        - NO_GO: any critical findings or risk >= 75
        - CONDITIONAL: >3 high findings, risk >= 50, or
          release workflow not approved
        - GO: all other cases

        Args:
            risk_score: Combined risk score (0-100).
            findings: Dict with ``critical``, ``high``, ``total``.
            release_result: ReleasePreparationWorkflow result.

        Returns:
            One of ``"GO"``, ``"NO_GO"``, or ``"CONDITIONAL"``.

        """
        # Critical findings = immediate NO_GO
        if findings.get("critical", 0) > 0:
            return "NO_GO"

        # Very high risk = NO_GO
        if risk_score >= 75:
            return "NO_GO"

        # High findings or elevated risk = CONDITIONAL
        if findings.get("high", 0) > 3 or risk_score >= 50:
            return "CONDITIONAL"

        # Release workflow not approved = CONDITIONAL
        if release_result and release_result.final_output:
            if not release_result.final_output.get("approved", True):
                return "CONDITIONAL"

        return "GO"

    def _generate_recommendations(
        self,
        crew_report: dict | None,
        security_result: WorkflowResult | None,
        code_review_result: WorkflowResult | None,
        release_result: WorkflowResult | None,
    ) -> tuple[list[str], list[str], list[str]]:
        """Generate actionable blockers, warnings, and recommendations.

        Aggregates issues from all workflow results into three
        priority-ordered lists for the release decision report.

        Args:
            crew_report: SecurityAuditCrew results dict.
            security_result: SecurityAuditWorkflow result.
            code_review_result: CodeReviewWorkflow result.
            release_result: ReleasePreparationWorkflow result.

        Returns:
            Tuple of (blockers, warnings, recommendations) lists.

        """
        blockers = []
        warnings = []
        recommendations = []

        # Crew findings
        if crew_report:
            critical = crew_report.get("assessment", {}).get("critical_findings", [])
            for f in critical:
                blockers.append(f"Critical: {f.get('title', 'Unknown issue')}")

            high = crew_report.get("assessment", {}).get("high_findings", [])
            for f in high[:3]:  # Top 3
                warnings.append(f"High: {f.get('title', 'Unknown issue')}")

        # Security audit findings
        if security_result and security_result.final_output:
            assessment = security_result.final_output.get("assessment", {})
            if assessment.get("risk_level") == "critical":
                blockers.append("Security audit identified critical risk level")
            elif assessment.get("risk_level") == "high":
                warnings.append("Security audit identified high risk level")

        # Code review verdict
        if code_review_result and code_review_result.final_output:
            verdict = code_review_result.final_output.get("verdict", "")
            if verdict == "reject":
                blockers.append("Code review: Changes rejected")
            elif verdict == "request_changes":
                warnings.append("Code review: Changes requested")

        # Release prep blockers
        if release_result and release_result.final_output:
            for b in release_result.final_output.get("blockers", []):
                blockers.append(f"Release: {b}")
            for w in release_result.final_output.get("warnings", []):
                warnings.append(f"Release: {w}")

        # General recommendations
        if not blockers and not warnings:
            recommendations.append("All checks passed - ready for release")
        elif blockers:
            recommendations.append("Address all blockers before release")
            recommendations.append("Consider running security audit on fixes")
        elif warnings:
            recommendations.append("Review warnings before release")
            recommendations.append("Document accepted risks if proceeding")

        return blockers, warnings, recommendations

    @classmethod
    def for_pr_review(cls, files_changed: int = 0) -> "SecureReleasePipeline":
        """Create a pipeline optimized for PR review.

        Uses ``standard`` mode for small changes (<10 files) and
        ``full`` mode with crew for larger changes.

        Args:
            files_changed: Number of files in the PR.

        Returns:
            Configured SecureReleasePipeline instance.

        """
        return cls(
            mode="standard" if files_changed < 10 else "full",
            parallel_crew=True,
        )

    @classmethod
    def for_release(cls) -> "SecureReleasePipeline":
        """Create a pipeline for full release preparation.

        Uses ``full`` mode with thorough crew scanning depth.

        Returns:
            Configured SecureReleasePipeline instance.

        """
        return cls(
            mode="full",
            crew_config={"scan_depth": "thorough"},
        )


def format_secure_release_report(result: SecureReleaseResult) -> str:
    """Format secure release result as a human-readable report.

    Args:
        result: The SecureReleaseResult object

    Returns:
        Formatted report string

    """
    lines = []

    # Header with go/no-go decision
    go_no_go = result.go_no_go

    if go_no_go == "GO":
        status_icon = "✅"
        status_text = "READY FOR RELEASE"
    elif go_no_go == "CONDITIONAL":
        status_icon = "⚠️"
        status_text = "CONDITIONAL APPROVAL"
    else:
        status_icon = "❌"
        status_text = "RELEASE BLOCKED"

    lines.append("=" * 60)
    lines.append("SECURE RELEASE REPORT")
    lines.append("=" * 60)
    lines.append("")
    lines.append(f"Decision: {status_icon} {go_no_go} - {status_text}")
    lines.append(f"Risk Score: {result.combined_risk_score:.1f}/100")
    lines.append(f"Pipeline Mode: {result.mode.upper()}")
    lines.append(f"Crew Enabled: {'Yes' if result.crew_enabled else 'No'}")
    lines.append("")

    # Findings summary
    lines.append("-" * 60)
    lines.append("FINDINGS SUMMARY")
    lines.append("-" * 60)
    lines.append(f"Total Findings: {result.total_findings}")
    lines.append(f"  🔴 Critical: {result.critical_count}")
    lines.append(f"  🟠 High: {result.high_count}")
    lines.append("")

    # Blockers
    if result.blockers:
        lines.append("-" * 60)
        lines.append("🚫 BLOCKERS (Must Fix Before Release)")
        lines.append("-" * 60)
        for blocker in result.blockers:
            lines.append(f"  • {blocker}")
        lines.append("")

    # Warnings
    if result.warnings:
        lines.append("-" * 60)
        lines.append("⚠️  WARNINGS")
        lines.append("-" * 60)
        for warning in result.warnings:
            lines.append(f"  • {warning}")
        lines.append("")

    # Recommendations
    if result.recommendations:
        lines.append("-" * 60)
        lines.append("💡 RECOMMENDATIONS")
        lines.append("-" * 60)
        for rec in result.recommendations:
            lines.append(f"  • {rec}")
        lines.append("")

    # Individual workflow summaries
    lines.append("-" * 60)
    lines.append("WORKFLOW RESULTS")
    lines.append("-" * 60)

    # Crew results
    if result.crew_report:
        crew_risk = result.crew_report.get("risk_score", 0)
        crew_findings = result.crew_report.get("finding_count", 0)
        crew_icon = "✅" if crew_risk < 50 else "⚠️" if crew_risk < 75 else "❌"
        lines.append(
            f"  {crew_icon} SecurityAuditCrew: {crew_findings} findings, risk {crew_risk}/100",
        )
    elif result.crew_enabled:
        lines.append("  ⏭️ SecurityAuditCrew: Skipped or failed")

    # Security audit
    if result.security_audit:
        sec_output = result.security_audit.final_output or {}
        assessment = sec_output.get("assessment", {})
        sec_risk = assessment.get("risk_score", 0)
        sec_level = assessment.get("risk_level", "unknown")
        sec_icon = "✅" if sec_risk < 50 else "⚠️" if sec_risk < 75 else "❌"
        lines.append(f"  {sec_icon} SecurityAudit: {sec_level} risk ({sec_risk}/100)")

    # Code review
    if result.code_review:
        cr_output = result.code_review.final_output or {}
        verdict = cr_output.get("verdict", "unknown")
        cr_icon = "✅" if verdict == "approve" else "⚠️" if verdict == "request_changes" else "❌"
        lines.append(f"  {cr_icon} CodeReview: {verdict}")

    # Release prep
    if result.release_prep:
        rp_output = result.release_prep.final_output or {}
        approved = rp_output.get("approved", False)
        confidence = rp_output.get("confidence", "unknown")
        rp_icon = "✅" if approved else "❌"
        lines.append(
            f"  {rp_icon} ReleasePrep: {'Approved' if approved else 'Not Approved'} ({confidence} confidence)",
        )

    lines.append("")

    # Cost and duration
    lines.append("-" * 60)
    lines.append("EXECUTION DETAILS")
    lines.append("-" * 60)
    lines.append(f"Total Cost: ${result.total_cost:.4f}")
    lines.append(f"Duration: {result.total_duration_ms:.0f}ms")
    lines.append("")

    # Footer
    lines.append("=" * 60)
    if go_no_go == "GO":
        lines.append("All security checks passed. Proceed with release.")
    elif go_no_go == "CONDITIONAL":
        lines.append("Review warnings before proceeding with release.")
    else:
        lines.append("Address all blockers before release can proceed.")
    lines.append("=" * 60)

    return "\n".join(lines)
