"""
EventheOdds LangGraph SEO Service
FastAPI server for LangGraph workflow orchestration
"""

import os
import uuid
import traceback
from datetime import datetime
from typing import Optional, List

from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
import uvicorn

from checkpointer.redis_checkpointer import RedisCheckpointer
from state.campaign_state import (
    SEOCampaignState,
    create_initial_state,
    CampaignPhase,
    LinkOpportunityStatus,
)
from graphs.seo_campaign_graph import (
    create_seo_campaign_graph,
    run_campaign,
    resume_campaign,
)
from graphs.zero_click_graph import create_zero_click_graph
from graphs.link_approval_graph import create_link_approval_graph


# Initialize FastAPI app
app = FastAPI(
    title="EventheOdds LangGraph SEO Service",
    description="LangGraph-based SEO campaign orchestration with human-in-the-loop approvals",
    version="1.0.0",
)

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "http://127.0.0.1:3000",
        "https://eventheodds.ai",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize Redis checkpointer
checkpointer = RedisCheckpointer(
    redis_url=os.getenv("REDIS_URL", "redis://localhost:6379/0"),
    prefix="langgraph:seo:",
    ttl_seconds=86400 * 30,  # 30 days
)

# In-memory campaign state cache (for quick lookups)
campaign_cache: dict[str, SEOCampaignState] = {}


# Request/Response Models
class StartCampaignRequest(BaseModel):
    site_id: str = Field(..., description="Site ID to run campaign for")
    template: str = Field(
        default="zero_click_authority",
        description="Campaign template: zero_click_authority, link_building, full_campaign"
    )
    config: Optional[dict] = Field(default=None, description="Optional campaign configuration")


class StartCampaignResponse(BaseModel):
    campaign_id: str
    site_id: str
    status: str
    current_phase: str
    message: str


class CampaignStateResponse(BaseModel):
    campaign_id: str
    site_id: str
    campaign_type: str
    current_phase: str
    status: str
    pending_approvals: List[dict]
    requires_human_review: bool
    metrics: dict
    started_at: str
    updated_at: str


class ApprovalRequest(BaseModel):
    edited_pitch: Optional[str] = Field(default=None, description="Optional edited pitch text")


class RejectRequest(BaseModel):
    reason: str = Field(..., description="Reason for rejection")


class ApprovalResponse(BaseModel):
    opportunity_id: str
    status: str
    message: str


class ResumeCampaignResponse(BaseModel):
    campaign_id: str
    status: str
    current_phase: str
    message: str


# Health check
@app.get("/health")
async def health_check():
    return {
        "status": "healthy",
        "service": "langgraph-seo",
        "version": "1.0.0",
        "timestamp": datetime.utcnow().isoformat(),
    }


# Campaign Management Endpoints
@app.post("/api/campaigns/start", response_model=StartCampaignResponse)
async def start_campaign(
    request: StartCampaignRequest,
    background_tasks: BackgroundTasks,
):
    """
    Start a new SEO campaign

    Templates:
    - zero_click_authority: Focus on SERP features and AI Overviews
    - link_building: Focus on safe link acquisition with human approval
    - full_campaign: Complete SEO workflow with all phases
    """
    campaign_id = str(uuid.uuid4())

    # Create initial state
    initial_state = create_initial_state(
        site_id=request.site_id,
        campaign_id=campaign_id,
        campaign_type=request.template,
        config=request.config or {},
    )

    # Cache initial state
    campaign_cache[campaign_id] = initial_state

    # Run campaign in background
    async def run_campaign_task():
        nonlocal initial_state
        try:
            if request.template == "zero_click_authority":
                graph = create_zero_click_graph(checkpointer)
            elif request.template == "link_building":
                graph = create_link_approval_graph(checkpointer)
            else:
                graph = create_seo_campaign_graph(checkpointer)

            thread_config = {"configurable": {"thread_id": campaign_id}}
            print(f"[Campaign] {campaign_id}: Starting graph execution for template '{request.template}'")
            final_state = await graph.ainvoke(initial_state, thread_config)
            campaign_cache[campaign_id] = final_state
            print(f"[Campaign] {campaign_id}: Completed - phase: {final_state.get('current_phase')}")
        except Exception as e:
            error_msg = str(e) if str(e) else repr(e)
            print(f"[CampaignError] {campaign_id}: {error_msg}")
            print(f"[CampaignError] {campaign_id}: Traceback:\n{traceback.format_exc()}")
            initial_state["current_phase"] = CampaignPhase.FAILED.value
            initial_state["errors"] = initial_state.get("errors", []) + [{
                "phase": "execution",
                "message": error_msg,
                "timestamp": datetime.utcnow().isoformat(),
            }]
            campaign_cache[campaign_id] = initial_state

    background_tasks.add_task(run_campaign_task)

    return StartCampaignResponse(
        campaign_id=campaign_id,
        site_id=request.site_id,
        status="started",
        current_phase=CampaignPhase.RESEARCH.value,
        message=f"Campaign started with template '{request.template}'",
    )


@app.post("/api/campaigns/{campaign_id}/resume", response_model=ResumeCampaignResponse)
async def resume_campaign_endpoint(
    campaign_id: str,
    background_tasks: BackgroundTasks,
):
    """
    Resume a paused campaign after human approval

    Call this after approving/rejecting link opportunities to continue the workflow.
    """
    # Get current state
    state = campaign_cache.get(campaign_id)
    if not state:
        raise HTTPException(status_code=404, detail="Campaign not found")

    if state.get("current_phase") == CampaignPhase.COMPLETED.value:
        raise HTTPException(status_code=400, detail="Campaign already completed")

    # Check if still waiting for approvals
    pending = state.get("pending_approvals", [])
    still_pending = [o for o in pending if o.get("status") == LinkOpportunityStatus.PENDING.value]

    if still_pending:
        return ResumeCampaignResponse(
            campaign_id=campaign_id,
            status="pending",
            current_phase=state.get("current_phase", "unknown"),
            message=f"Still waiting for {len(still_pending)} approvals",
        )

    # Resume campaign
    async def resume_task():
        try:
            graph = create_seo_campaign_graph(checkpointer)
            thread_config = {"configurable": {"thread_id": campaign_id}}
            final_state = await graph.ainvoke(state, thread_config)
            campaign_cache[campaign_id] = final_state
        except Exception as e:
            print(f"[ResumeError] {campaign_id}: {e}")

    background_tasks.add_task(resume_task)

    return ResumeCampaignResponse(
        campaign_id=campaign_id,
        status="resuming",
        current_phase=state.get("current_phase", "unknown"),
        message="Campaign resuming after approval",
    )


@app.get("/api/campaigns/{campaign_id}/state", response_model=CampaignStateResponse)
async def get_campaign_state(campaign_id: str):
    """
    Get current campaign state and pending approvals
    """
    state = campaign_cache.get(campaign_id)
    if not state:
        raise HTTPException(status_code=404, detail="Campaign not found")

    # Build metrics summary
    metrics = {
        "pages_generated": state.get("pages_generated", 0),
        "pages_optimized": state.get("pages_optimized", 0),
        "snippets_captured": state.get("snippets_captured", 0),
        "links_approved": state.get("links_approved", 0),
        "links_acquired": state.get("links_acquired", 0),
        "errors": len(state.get("errors", [])),
        "warnings": len(state.get("warnings", [])),
    }

    # Determine status
    current_phase = state.get("current_phase", "unknown")
    if current_phase == CampaignPhase.COMPLETED.value:
        status = "completed"
    elif current_phase == CampaignPhase.FAILED.value:
        status = "failed"
    elif state.get("requires_human_review"):
        status = "awaiting_approval"
    else:
        status = "running"

    return CampaignStateResponse(
        campaign_id=campaign_id,
        site_id=state.get("site_id", ""),
        campaign_type=state.get("campaign_type", ""),
        current_phase=current_phase,
        status=status,
        pending_approvals=state.get("pending_approvals", []),
        requires_human_review=state.get("requires_human_review", False),
        metrics=metrics,
        started_at=state.get("started_at", ""),
        updated_at=state.get("updated_at", ""),
    )


# Approval Endpoints
@app.post("/api/approvals/{opportunity_id}/approve", response_model=ApprovalResponse)
async def approve_link(
    opportunity_id: str,
    request: ApprovalRequest,
    campaign_id: Optional[str] = None,
):
    """
    Approve a link opportunity

    Optionally provide an edited pitch to use instead of the draft.
    """
    # Find the opportunity across campaigns
    found_campaign_id = campaign_id
    found_opp = None

    if campaign_id:
        state = campaign_cache.get(campaign_id)
        if state:
            for opp in state.get("link_opportunities", []):
                if opp.get("id") == opportunity_id:
                    found_opp = opp
                    break
    else:
        # Search all campaigns
        for cid, state in campaign_cache.items():
            for opp in state.get("link_opportunities", []):
                if opp.get("id") == opportunity_id:
                    found_opp = opp
                    found_campaign_id = cid
                    break
            if found_opp:
                break

    if not found_opp:
        raise HTTPException(status_code=404, detail="Opportunity not found")

    # Approve the opportunity
    found_opp["status"] = LinkOpportunityStatus.APPROVED.value
    found_opp["approved_at"] = datetime.utcnow().isoformat()

    if request.edited_pitch:
        found_opp["pitch_approved"] = request.edited_pitch

    # Update pending approvals
    if found_campaign_id:
        state = campaign_cache[found_campaign_id]
        state["pending_approvals"] = [
            o for o in state.get("pending_approvals", [])
            if o.get("id") != opportunity_id
        ]
        state["links_approved"] = state.get("links_approved", 0) + 1

        # Check if all approved
        if not state["pending_approvals"]:
            state["requires_human_review"] = False

    return ApprovalResponse(
        opportunity_id=opportunity_id,
        status="approved",
        message="Link opportunity approved",
    )


@app.post("/api/approvals/{opportunity_id}/reject", response_model=ApprovalResponse)
async def reject_link(
    opportunity_id: str,
    request: RejectRequest,
    campaign_id: Optional[str] = None,
):
    """
    Reject a link opportunity
    """
    # Find the opportunity
    found_campaign_id = campaign_id
    found_opp = None

    if campaign_id:
        state = campaign_cache.get(campaign_id)
        if state:
            for opp in state.get("link_opportunities", []):
                if opp.get("id") == opportunity_id:
                    found_opp = opp
                    break
    else:
        for cid, state in campaign_cache.items():
            for opp in state.get("link_opportunities", []):
                if opp.get("id") == opportunity_id:
                    found_opp = opp
                    found_campaign_id = cid
                    break
            if found_opp:
                break

    if not found_opp:
        raise HTTPException(status_code=404, detail="Opportunity not found")

    # Reject the opportunity
    found_opp["status"] = LinkOpportunityStatus.REJECTED.value
    found_opp["rejection_reason"] = request.reason

    # Update pending approvals
    if found_campaign_id:
        state = campaign_cache[found_campaign_id]
        state["pending_approvals"] = [
            o for o in state.get("pending_approvals", [])
            if o.get("id") != opportunity_id
        ]

        # Check if all processed
        if not state["pending_approvals"]:
            state["requires_human_review"] = False

    return ApprovalResponse(
        opportunity_id=opportunity_id,
        status="rejected",
        message=f"Link opportunity rejected: {request.reason}",
    )


# List campaigns
@app.get("/api/campaigns")
async def list_campaigns(
    status: Optional[str] = None,
    site_id: Optional[str] = None,
):
    """
    List all campaigns, optionally filtered by status or site
    """
    campaigns = []

    for cid, state in campaign_cache.items():
        # Filter by site_id
        if site_id and state.get("site_id") != site_id:
            continue

        # Determine status
        current_phase = state.get("current_phase", "unknown")
        if current_phase == CampaignPhase.COMPLETED.value:
            camp_status = "completed"
        elif current_phase == CampaignPhase.FAILED.value:
            camp_status = "failed"
        elif state.get("requires_human_review"):
            camp_status = "awaiting_approval"
        else:
            camp_status = "running"

        # Filter by status
        if status and camp_status != status:
            continue

        campaigns.append({
            "campaign_id": cid,
            "site_id": state.get("site_id"),
            "campaign_type": state.get("campaign_type"),
            "current_phase": current_phase,
            "status": camp_status,
            "pending_approvals": len(state.get("pending_approvals", [])),
            "started_at": state.get("started_at"),
            "updated_at": state.get("updated_at"),
        })

    return {"campaigns": campaigns}


# Pending approvals across all campaigns
@app.get("/api/approvals/pending")
async def get_pending_approvals():
    """
    Get all pending link approvals across all campaigns
    """
    pending = []

    for cid, state in campaign_cache.items():
        for opp in state.get("pending_approvals", []):
            if opp.get("status") == LinkOpportunityStatus.PENDING.value:
                pending.append({
                    "campaign_id": cid,
                    "site_id": state.get("site_id"),
                    **opp,
                })

    return {"pending_approvals": pending, "total": len(pending)}


# Startup/shutdown
@app.on_event("startup")
async def startup():
    print("[LangGraph SEO Service] Starting up...")
    # Pre-warm Redis connection
    try:
        await checkpointer._get_client()
        print("[LangGraph SEO Service] Redis connection established")
    except Exception as e:
        print(f"[LangGraph SEO Service] Redis connection failed: {e}")


@app.on_event("shutdown")
async def shutdown():
    print("[LangGraph SEO Service] Shutting down...")
    await checkpointer.close()


# Run server
if __name__ == "__main__":
    port = int(os.getenv("LANGGRAPH_PORT", "8001"))
    host = os.getenv("LANGGRAPH_HOST", "127.0.0.1")

    uvicorn.run(
        "app:app",
        host=host,
        port=port,
        reload=os.getenv("ENV", "development") == "development",
    )
