"""FastAPI application for audit evidence storage."""
from fastapi import FastAPI, Depends, HTTPException, Header, status
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
import json
import uuid
import os
from pathlib import Path
from datetime import datetime
from dotenv import load_dotenv

# Load .env from project root (parent directory) if it exists
# On Vercel, environment variables are set directly, so .env is optional
env_path = Path(__file__).parent.parent / '.env'
if env_path.exists():
    load_dotenv(dotenv_path=env_path)
else:
    # Try loading from current directory as fallback
    load_dotenv()

from api.utils import hash_api_key
from typing import Dict
from api.auth import verify_api_key
from api.routes import auth, dashboard, github, projects, policies, regulators, billing, api_keys, ai_features, templates

app = FastAPI(
    title="Verity Audit Evidence API",
    description="Immutable audit evidence storage for AI governance",
    version="1.0.0"
)

# Configure CORS
# Get frontend URL from environment, with fallbacks
frontend_url = os.getenv("FRONTEND_URL", "http://localhost:3000")
cors_origins = [
    "http://localhost:3000",  # Website
    "http://localhost:5173",   # Vite dev server (if different port)
    "http://localhost:5174",   # Vite dev server (if different port)
    frontend_url,
]

# Add Vercel frontend domain if provided
vercel_frontend = os.getenv("VERCEL_FRONTEND_URL")
if vercel_frontend:
    cors_origins.append(vercel_frontend)
    # Also add without protocol if needed
    if not vercel_frontend.startswith("http"):
        cors_origins.append(f"https://{vercel_frontend}")

# In production (Railway), allow all origins to avoid CORS issues
# You should set FRONTEND_URL in Railway to your exact Vercel domain for better security
is_production = os.getenv("RAILWAY_ENVIRONMENT") or os.getenv("RAILWAY_ENVIRONMENT_NAME") or os.getenv("PORT")
if is_production:
    # Allow all origins in production (when using "*", allow_credentials must be False)
    cors_origins = ["*"]
    allow_creds = False
    pass
else:
    allow_creds = True

app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=allow_creds,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Health check endpoint
@app.get("/health")
async def health_check():
    """Health check endpoint."""
    return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}

# Include routers
app.include_router(auth.router)
app.include_router(dashboard.router)
app.include_router(github.router)
app.include_router(projects.router)
app.include_router(policies.router)
app.include_router(regulators.router)
app.include_router(billing.router)
app.include_router(api_keys.router)
app.include_router(ai_features.router)
app.include_router(templates.router)


def get_api_key(
    authorization: Optional[str] = Header(None)
) -> Dict:
    """Dependency to verify API key."""
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing or invalid authorization header"
        )
    
    api_key = authorization.replace("Bearer ", "")
    from api.auth import verify_api_key
    api_key_obj = verify_api_key(api_key)
    
    if not api_key_obj:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or rate-limited API key"
        )
    
    return api_key_obj


@app.on_event("startup")
async def startup():
    """Initialize database on startup."""
    # No longer using SQLite - Firestore is initialized automatically


@app.post("/v1/audits")
async def create_audit(
    report: dict,
    api_key_obj: Dict = Depends(get_api_key)
):
    """
    Store audit evidence (append-only).
    
    Requires:
    - Valid API key in Authorization header
    - report.json in request body
    - Organization and project IDs in report metadata (or use API key's scope)
    """
    # Extract audit_id from report
    audit_id = report.get("audit_id")
    if not audit_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Missing audit_id in report"
        )
    
    # Security check: Ensure no raw data in report
    # Only allow metrics, hashes, metadata, and aggregated counts
    forbidden_keys = ["raw_data", "data_rows", "dataset_rows", "sample_data"]
    for key in forbidden_keys:
        if key in report:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Raw data not allowed in audit reports: {key}"
            )
    
    # Check if audit already exists (append-only: no overwrites)
    from api.firestore_db import get_audit
    existing = get_audit(audit_id)
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Audit {audit_id} already exists (append-only storage)"
        )
    
    # Get organization and project from API key scope
    org_id = api_key_obj['organization_id']
    project_id = api_key_obj.get('project_id')
    
    # Allow override from report metadata if provided
    if "organization_id" in report:
        org_id = report["organization_id"]
    if "project_id" in report:
        project_id = report["project_id"]
    
    if not project_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="project_id required (either from API key scope or report metadata)"
        )
    
    # Verify organization and project exist
    from api.firestore_db import get_organization, get_project
    org = get_organization(org_id)
    if not org:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Organization {org_id} not found"
        )
    
    # Check subscription status - must be active for uploads
    if org.get('subscription_status') != "active":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=f"Active subscription required. Current status: {org.get('subscription_status')}. Please update your payment method or subscribe to a plan."
        )
    
    project = get_project(project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project {project_id} not found"
        )
    
    # Verify project belongs to organization
    if project.get('organization_id') != org_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Project does not belong to organization"
        )
    
    # Extract hashes from report
    file_hashes = report.get("file_hashes", {})
    dataset_hash = file_hashes.get("dataset", {}).get("sha256", "")
    model_hash = file_hashes.get("model", {}).get("sha256", "")
    config_hash = file_hashes.get("config", {}).get("sha256", "")
    
    # Extract commit context (from CI environment or report metadata)
    metadata = report.get("metadata", {})
    commit_sha = report.get("commit_sha") or metadata.get("commit_sha") or os.getenv("GITHUB_SHA")
    branch = report.get("branch") or metadata.get("branch") or os.getenv("GITHUB_REF_NAME")
    pr_number = report.get("pr_number") or metadata.get("pr_number")
    repo = report.get("repo") or metadata.get("repo") or os.getenv("GITHUB_REPOSITORY")
    workflow_run_url = metadata.get("workflow_run_url") or os.getenv("GITHUB_SERVER_URL") and os.getenv("GITHUB_REPOSITORY") and os.getenv("GITHUB_RUN_ID") and f"{os.getenv('GITHUB_SERVER_URL')}/{os.getenv('GITHUB_REPOSITORY')}/actions/runs/{os.getenv('GITHUB_RUN_ID')}"
    
    # Store workflow_run_url in report metadata if not already there
    if workflow_run_url and "workflow_run_url" not in metadata:
        metadata["workflow_run_url"] = workflow_run_url
        report["metadata"] = metadata
    
    # Extract audit results
    overall_passed = report.get("overall_passed", False)
    policy_version = report.get("policy_version", 1)
    
    # Phase 2-4: Enrich report with governance checks and ethics mapping if missing
    # Check organization subscription for feature flags
    from api.billing import check_plan_access
    from api.constants import Plan
    has_evidence_storage = check_plan_access(org, "evidence_storage")
    has_api_access = check_plan_access(org, "api_access")
    
    # Get organization plan
    org_plan = org.get('plan', 'free').lower()
    
    # Filter thresholds based on plan
    # Starter plan: Only check 3 basic thresholds
    STARTER_THRESHOLDS = {
        "demographic_parity_diff": 0.10,
        "equal_opportunity_diff": 0.10,
        "disparate_impact_ratio_min": 0.80
    }
    
    thresholds = report.get("thresholds", {})
    if org_plan == "starter":
        # Filter to only starter thresholds
        filtered_thresholds = {}
        for key in STARTER_THRESHOLDS.keys():
            if key in thresholds:
                filtered_thresholds[key] = thresholds[key]
            else:
                # Use default values if not in config
                filtered_thresholds[key] = STARTER_THRESHOLDS[key]
        # Also check for any threshold keys that match the starter pattern
        for key, value in thresholds.items():
            if any(starter_key in key.lower() for starter_key in ["demographic_parity_diff", "equal_opportunity_diff", "disparate_impact_ratio_min"]):
                filtered_thresholds[key] = value
        thresholds = filtered_thresholds
        # Update report thresholds
        report["thresholds"] = thresholds
        if "fairness_metrics" in report:
            report["fairness_metrics"]["thresholds"] = thresholds
    
    # Governance checks and ACM ethics mapping only for Pro/Enterprise
    include_governance = org_plan in ["pro", "enterprise"]
    include_ethics = org_plan in ["pro", "enterprise"]
    
    # If report doesn't have expanded structure, enrich it
    if "governance_checks" not in report or "ethics_mapping" not in report:
        from audit.governance import check_governance_config
        from audit.ethics import map_ethics_to_evidence
        
        # Get config path if available
        config_path = None
        if "file_hashes" in report and "config" in report["file_hashes"]:
            # We can't reconstruct the actual path, but we know config exists
            from pathlib import Path
            config_path = Path("config.yml")  # Placeholder - actual path not stored
        
        # Get sensitive attrs from report
        sensitive_attrs = report.get("sensitive_attributes", [])
        metrics = report.get("metrics", {})
        
        # Add governance checks (only for Pro/Enterprise)
        governance_checks = None
        if include_governance:
            governance_checks = check_governance_config(
                config_path=config_path,
                thresholds=thresholds,
                sensitive_attrs=sensitive_attrs,
                has_evidence_storage=has_evidence_storage,
                has_api_access=has_api_access
            )
        
        # Add ethics mapping (only for Pro/Enterprise)
        ethics_mapping = None
        if include_ethics:
            ethics_mapping = map_ethics_to_evidence(
                metrics=metrics,
                thresholds=thresholds,
                governance_checks=governance_checks or {},
                has_evidence_storage=has_evidence_storage
            )
        
        # Update report with new structure (maintain backward compatibility)
        if "fairness_metrics" not in report:
            report["fairness_metrics"] = {
                "metrics": metrics,
                "thresholds": thresholds,
                "summary": report.get("summary", {})
            }
        
        if governance_checks:
            report["governance_checks"] = governance_checks
        if ethics_mapping:
            report["ethics_mapping"] = ethics_mapping
        
        # Update evidence metadata
        if "evidence_metadata" not in report:
            report["evidence_metadata"] = {
                "audit_id": report.get("audit_id"),
                "timestamp": report.get("timestamp"),
                "file_hashes": report.get("file_hashes", {}),
                "model_metadata": report.get("model_metadata", {}),
                "dataset_metadata": report.get("dataset_metadata", {}),
                "sensitive_attributes": sensitive_attrs,
                "retention_status": "active" if has_evidence_storage else "disabled",
                "immutability": "enforced" if has_evidence_storage else "not_enforced"
            }
        else:
            # Update existing evidence metadata
            report["evidence_metadata"]["retention_status"] = "active" if has_evidence_storage else "disabled"
            report["evidence_metadata"]["immutability"] = "enforced" if has_evidence_storage else "not_enforced"
    
    # Store audit (append-only)
    from api.firestore_db import create_audit as fs_create_audit
    
    audit_data = {
        'id': audit_id,
        'organization_id': org_id,
        'project_id': project_id,
        'report_json': json.dumps(report),
        'dataset_hash': dataset_hash,
        'model_hash': model_hash,
        'config_hash': config_hash,
        'created_at': datetime.fromisoformat(report.get("timestamp", datetime.utcnow().isoformat())),
        'commit_sha': commit_sha,
        'branch': branch,
        'pr_number': pr_number,
        'repo': repo,
        'overall_passed': overall_passed,
        'policy_version': policy_version
    }
    
    fs_create_audit(audit_data)
    
    # Get organization plan to include in response
    org_plan = org.get('plan', 'free').lower()
    
    response_data = {
        "audit_id": audit_id,
        "status": "stored",
        "created_at": audit_data['created_at'].isoformat() if isinstance(audit_data['created_at'], datetime) else audit_data['created_at'],
        "organization_plan": org_plan  # Include in response body for CLI detection
    }
    
    # Return response with plan in both body and headers
    response = JSONResponse(
        content=response_data,
        headers={
            "X-Organization-Plan": org_plan
        }
    )
    return response


@app.get("/v1/audits/{audit_id}")
async def get_audit(
    audit_id: str,
    api_key_obj: Dict = Depends(get_api_key)
):
    """
    Retrieve audit evidence by audit_id.
    
    Requires:
    - Valid API key in Authorization header
    - API key must have access to the audit's organization/project
    """
    from api.firestore_db import get_audit as fs_get_audit
    
    audit = fs_get_audit(audit_id)
    
    if not audit:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Audit {audit_id} not found"
        )
    
    # Check access: API key must be for same org, and project must match if key is project-scoped
    if audit.get('organization_id') != api_key_obj['organization_id']:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Access denied"
        )
    
    if api_key_obj.get('project_id') and audit.get('project_id') != api_key_obj['project_id']:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Access denied: project-scoped key"
        )
    
    # Return full report
    report_json = audit.get('report_json', '{}')
    if isinstance(report_json, str):
        report = json.loads(report_json)
    else:
        report = report_json
    return report


@app.get("/health")
async def health():
    """Health check endpoint."""
    return {"status": "ok"}


@app.get("/")
async def root():
    """Root endpoint - redirect to health check."""
    return {"message": "Verity API", "status": "running", "docs": "/docs"}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

