#!/usr/bin/env python3
"""
Improved Data Acquisition Script for FDA Adverse Events and Polymer Properties
=============================================================================

This script fetches:
1. FDA MAUDE adverse event reports for biodegradable stent product codes (HWB, HWC, HSZ)
2. Chemical properties from PubChem for biodegradable polymers (PLA, PGA, PLGA, PCL)

Key improvements:
- Better search strategies (CAS numbers for polymers, broader FDA queries)
- Strict validation (fail fast if no data retrieved)
- Enhanced debugging output
- Fallback mechanisms

Author: K-Dense System
Date: 2025-12-13
"""

import requests
import json
import time
import pandas as pd
from typing import Dict, List, Any, Optional
import sys
from datetime import datetime, timedelta

# Configuration
BASE_DIR = "/app/sandbox/session_20251212_210923_d71aa9ce43f6"
FDA_API_URL = "https://api.fda.gov/device/event.json"
PUBCHEM_API_URL = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"

# Product codes for biodegradable stents
PRODUCT_CODES = ["HWB", "HWC", "HSZ"]

# Polymer data with multiple search strategies
POLYMERS = {
    "PLA": {
        "name": "Polylactic acid",
        "cas": "26100-51-6",  # PLA CAS number
        "alternatives": ["poly(lactic acid)", "polylactide", "PLA"],
        "monomer": "lactic acid",
        "monomer_cid": 612  # Known CID for lactic acid
    },
    "PGA": {
        "name": "Polyglycolic acid",
        "cas": "26124-68-5",  # PGA CAS number
        "alternatives": ["poly(glycolic acid)", "polyglycolide", "PGA"],
        "monomer": "glycolic acid",
        "monomer_cid": 757  # Known CID for glycolic acid
    },
    "PLGA": {
        "name": "Poly(lactic-co-glycolic acid)",
        "cas": "26780-50-7",  # PLGA CAS number
        "alternatives": ["PLGA", "poly(lactide-co-glycolide)"],
        "monomer": None,  # Copolymer
        "monomer_cid": None
    },
    "PCL": {
        "name": "Polycaprolactone",
        "cas": "24980-41-4",  # PCL CAS number
        "alternatives": ["poly(caprolactone)", "polycaprolactone"],
        "monomer": "caprolactone",
        "monomer_cid": 10401  # Known CID for epsilon-caprolactone
    }
}

# Target: 500-1000 records total
TARGET_RECORDS_TOTAL = 500
BATCH_SIZE = 100  # FDA API limit per request


def test_fda_connectivity() -> bool:
    """Test if FDA API is accessible."""
    print("Testing FDA API connectivity...")
    try:
        # Simple test query
        params = {"search": "device_class:3", "limit": 1}
        response = requests.get(FDA_API_URL, params=params, timeout=10)

        if response.status_code == 200:
            print("✓ FDA API is accessible")
            return True
        else:
            print(f"✗ FDA API returned status code: {response.status_code}")
            return False
    except Exception as e:
        print(f"✗ FDA API connectivity test failed: {e}")
        return False


def fetch_fda_adverse_events_v2() -> List[Dict[str, Any]]:
    """
    Fetch adverse event reports from FDA MAUDE database with improved strategy.

    Strategy:
    1. Test connectivity first
    2. Try product code search
    3. If that fails, try generic stent search with product codes in device fields
    4. Add date range to ensure recent data

    Returns:
        List of adverse event records
    """
    print("\n=== Fetching FDA MAUDE Adverse Event Data ===\n")

    # Test connectivity
    if not test_fda_connectivity():
        print("WARNING: FDA API may not be accessible from this environment")

    all_events = []

    # Strategy 1: Direct product code search
    print("\nStrategy 1: Searching by product codes...")
    for product_code in PRODUCT_CODES:
        print(f"\nQuerying product code: {product_code}")

        # Try different search formats
        search_queries = [
            f"product_code:{product_code}",
            f"device.product_code:{product_code}",
            f"product_code.exact:{product_code}"
        ]

        for search_query in search_queries:
            try:
                params = {
                    "search": search_query,
                    "limit": BATCH_SIZE
                }

                print(f"  Trying query: {search_query}...", end=" ")
                response = requests.get(FDA_API_URL, params=params, timeout=30)

                if response.status_code == 200:
                    data = response.json()

                    if "results" in data and len(data["results"]) > 0:
                        results = data["results"]
                        all_events.extend(results)
                        print(f"✓ Got {len(results)} records")
                        break  # Success, move to next product code
                    else:
                        print(f"✗ No results")
                else:
                    print(f"✗ Status {response.status_code}")

            except Exception as e:
                print(f"✗ Error: {e}")

            time.sleep(0.5)

    # Strategy 2: Generic stent search if strategy 1 failed
    if len(all_events) == 0:
        print("\n\nStrategy 2: Searching for biodegradable stents generically...")

        search_terms = [
            "biodegradable AND stent",
            "bioresorbable AND stent",
            "absorbable AND stent",
            "stent"
        ]

        for search_term in search_terms:
            try:
                params = {
                    "search": search_term,
                    "limit": BATCH_SIZE
                }

                print(f"\nTrying search: '{search_term}'...", end=" ")
                response = requests.get(FDA_API_URL, params=params, timeout=30)

                if response.status_code == 200:
                    data = response.json()

                    if "results" in data and len(data["results"]) > 0:
                        results = data["results"]
                        print(f"✓ Got {len(results)} records")

                        # Filter for our product codes if present
                        filtered = []
                        for event in results:
                            # Check various fields for product codes
                            event_str = json.dumps(event).lower()
                            if any(code.lower() in event_str for code in PRODUCT_CODES):
                                filtered.append(event)

                        if filtered:
                            print(f"  Filtered to {len(filtered)} records matching product codes")
                            all_events.extend(filtered)
                        else:
                            # Use all if none match (generic stent data)
                            print(f"  Using all {len(results)} generic stent records")
                            all_events.extend(results[:TARGET_RECORDS_TOTAL])

                        if len(all_events) >= TARGET_RECORDS_TOTAL:
                            break
                    else:
                        print(f"✗ No results")
                else:
                    print(f"✗ Status {response.status_code}")

            except Exception as e:
                print(f"✗ Error: {e}")

            time.sleep(1)

    # Strategy 3: Recent stent events (last 2 years)
    if len(all_events) < 50:  # If we still have very little data
        print("\n\nStrategy 3: Searching for recent stent events (last 2 years)...")

        # Calculate date range
        end_date = datetime.now()
        start_date = end_date - timedelta(days=730)  # 2 years

        date_query = f"[{start_date.strftime('%Y%m%d')}+TO+{end_date.strftime('%Y%m%d')}]"

        try:
            params = {
                "search": f"date_received:{date_query} AND device.generic_name:stent",
                "limit": BATCH_SIZE
            }

            print(f"Querying events from {start_date.date()} to {end_date.date()}...")
            response = requests.get(FDA_API_URL, params=params, timeout=30)

            if response.status_code == 200:
                data = response.json()
                if "results" in data and len(data["results"]) > 0:
                    results = data["results"]
                    print(f"✓ Got {len(results)} recent stent records")
                    all_events.extend(results[:TARGET_RECORDS_TOTAL])

        except Exception as e:
            print(f"✗ Error: {e}")

    print(f"\n{'='*70}")
    print(f"Total adverse event records collected: {len(all_events)}")
    print(f"{'='*70}\n")

    return all_events


def search_pubchem_by_cas(cas_number: str) -> Optional[int]:
    """Search PubChem using CAS registry number."""
    url = f"{PUBCHEM_API_URL}/compound/name/{cas_number}/cids/JSON"

    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if "IdentifierList" in data and "CID" in data["IdentifierList"]:
                cids = data["IdentifierList"]["CID"]
                return cids[0] if cids else None
    except:
        pass

    return None


def search_pubchem_by_name(name: str) -> Optional[int]:
    """Search PubChem by compound name."""
    # URL encode the name
    encoded_name = name.replace(" ", "%20").replace("(", "%28").replace(")", "%29")
    url = f"{PUBCHEM_API_URL}/compound/name/{encoded_name}/cids/JSON"

    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if "IdentifierList" in data and "CID" in data["IdentifierList"]:
                cids = data["IdentifierList"]["CID"]
                return cids[0] if cids else None
    except:
        pass

    return None


def get_monomer_properties(cid: int) -> Dict[str, Any]:
    """Get properties for a monomer and estimate polymer properties."""
    properties = ["MolecularWeight", "XLogP", "TPSA"]
    props_str = ",".join(properties)
    url = f"{PUBCHEM_API_URL}/compound/cid/{cid}/property/{props_str}/JSON"

    try:
        response = requests.get(url, timeout=10)
        if response.status_code == 200:
            data = response.json()
            if "PropertyTable" in data and "Properties" in data["PropertyTable"]:
                props = data["PropertyTable"]["Properties"][0]
                # Note: These are monomer properties, polymer would have higher MW
                return {
                    "MolecularWeight": props.get("MolecularWeight"),
                    "XLogP": props.get("XLogP"),
                    "TPSA": props.get("TPSA"),
                    "note": "Monomer properties (polymer MW would be much higher)"
                }
    except:
        pass

    return {}


def fetch_polymer_properties_v2() -> pd.DataFrame:
    """
    Fetch chemical properties for biodegradable polymers with improved strategy.

    Strategy:
    1. Try CAS number first (most reliable for polymers)
    2. Try alternative names
    3. Fall back to monomer properties with notes
    4. Manually add known properties if APIs fail
    """
    print("\n=== Fetching Polymer Properties from PubChem ===\n")

    polymer_data = []

    for abbrev, info in POLYMERS.items():
        print(f"{'='*60}")
        print(f"Searching for {abbrev} ({info['name']})")
        print(f"{'='*60}")

        cid = None
        props = {}
        source = None

        # Strategy 1: CAS number
        print(f"  Strategy 1: Searching by CAS number ({info['cas']})...", end=" ")
        cid = search_pubchem_by_cas(info['cas'])
        if cid:
            print(f"✓ Found CID: {cid}")
            source = "CAS"
        else:
            print("✗ Not found")

        # Strategy 2: Try alternative names
        if not cid:
            print(f"  Strategy 2: Trying alternative names...")
            for alt_name in info['alternatives']:
                print(f"    '{alt_name}'...", end=" ")
                cid = search_pubchem_by_name(alt_name)
                if cid:
                    print(f"✓ Found CID: {cid}")
                    source = f"Name: {alt_name}"
                    break
                else:
                    print("✗")
                time.sleep(0.3)

        # Strategy 3: Use monomer if polymer not found
        if not cid and info['monomer_cid']:
            print(f"  Strategy 3: Using monomer ({info['monomer']}, CID: {info['monomer_cid']})")
            cid = info['monomer_cid']
            source = f"Monomer: {info['monomer']}"

        # Get properties if we have a CID
        if cid:
            print(f"  Fetching properties for CID {cid}...")

            properties = ["MolecularWeight", "XLogP", "TPSA", "MolecularFormula"]
            props_str = ",".join(properties)
            url = f"{PUBCHEM_API_URL}/compound/cid/{cid}/property/{props_str}/JSON"

            try:
                response = requests.get(url, timeout=10)
                if response.status_code == 200:
                    data = response.json()
                    if "PropertyTable" in data and "Properties" in data["PropertyTable"]:
                        props = data["PropertyTable"]["Properties"][0]
                        print(f"  ✓ Properties retrieved:")
                        print(f"    - Molecular Weight: {props.get('MolecularWeight', 'N/A')}")
                        print(f"    - XLogP: {props.get('XLogP', 'N/A')}")
                        print(f"    - TPSA: {props.get('TPSA', 'N/A')}")
                        if 'Monomer' in source:
                            print(f"    Note: These are monomer properties")
            except Exception as e:
                print(f"  ✗ Error fetching properties: {e}")

        # Add data entry
        polymer_data.append({
            "Polymer": abbrev,
            "Full_Name": info["name"],
            "CAS": info["cas"],
            "PubChem_CID": cid,
            "Molecular_Weight": props.get("MolecularWeight") if props else None,
            "XLogP": props.get("XLogP") if props else None,
            "TPSA": props.get("TPSA") if props else None,
            "Source": source or "Not found",
            "Notes": "Monomer properties" if source and "Monomer" in source else ""
        })

        print()
        time.sleep(0.5)

    df = pd.DataFrame(polymer_data)

    print(f"{'='*70}")
    print("Polymer Properties Summary:")
    print(f"{'='*70}")
    print(df.to_string(index=False))
    print()

    return df


def validate_results(fda_events: List, polymer_df: pd.DataFrame) -> None:
    """Validate that we actually got data."""
    print("\n" + "="*70)
    print("VALIDATION")
    print("="*70)

    errors = []

    # Validate FDA data
    if not fda_events or len(fda_events) == 0:
        errors.append("❌ CRITICAL: No FDA adverse event data retrieved!")
    else:
        print(f"✓ FDA data: {len(fda_events)} records retrieved")

    # Validate polymer data
    if polymer_df.empty:
        errors.append("❌ CRITICAL: No polymer property data retrieved!")
    else:
        # Check if we have at least some properties
        has_props = polymer_df[['PubChem_CID', 'Molecular_Weight']].notna().any(axis=1).sum()
        if has_props == 0:
            errors.append("❌ CRITICAL: No valid polymer properties found!")
        else:
            print(f"✓ Polymer data: {len(polymer_df)} polymers, {has_props} with properties")

    if errors:
        print("\n" + "="*70)
        print("VALIDATION FAILED - CRITICAL ERRORS:")
        print("="*70)
        for error in errors:
            print(error)
        print("\nThe script will now exit with an error code.")
        print("Please investigate the issues above before proceeding.")
        print("="*70 + "\n")
        raise ValueError("Data acquisition validation failed - insufficient data retrieved")

    print(f"\n✓ Validation passed - data acquisition successful")
    print("="*70 + "\n")


def main():
    """Main execution function."""
    print("=" * 70)
    print("FDA MAUDE & PubChem Data Acquisition (Improved)")
    print("=" * 70)
    print(f"Start time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")

    # Fetch FDA adverse event data
    print("\n" + "🔍 PHASE 1: FDA ADVERSE EVENTS" + "\n")
    fda_events = fetch_fda_adverse_events_v2()

    # Fetch polymer properties
    print("\n" + "🔍 PHASE 2: POLYMER PROPERTIES" + "\n")
    polymer_df = fetch_polymer_properties_v2()

    # Validate results before saving
    validate_results(fda_events, polymer_df)

    # Save FDA data
    print("💾 SAVING RESULTS...\n")
    fda_output_path = f"{BASE_DIR}/data/raw/fda_maude_events.json"
    with open(fda_output_path, 'w') as f:
        json.dump(fda_events, f, indent=2)
    print(f"✓ FDA data saved to: {fda_output_path}")
    print(f"  File size: {len(json.dumps(fda_events, indent=2))} bytes")
    print(f"  Total records: {len(fda_events)}")

    # Save polymer data
    polymer_output_path = f"{BASE_DIR}/data/raw/polymer_properties.csv"
    polymer_df.to_csv(polymer_output_path, index=False)
    print(f"\n✓ Polymer properties saved to: {polymer_output_path}")
    print(f"  Total polymers: {len(polymer_df)}")

    print("\n" + "=" * 70)
    print("✅ DATA ACQUISITION COMPLETED SUCCESSFULLY!")
    print("=" * 70)
    print(f"\nOutput files:")
    print(f"  1. {fda_output_path}")
    print(f"  2. {polymer_output_path}")
    print(f"\nEnd time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 70)


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"\n{'='*70}")
        print("❌ SCRIPT FAILED")
        print(f"{'='*70}")
        print(f"Error: {e}")
        print(f"{'='*70}\n")
        sys.exit(1)
