"""
ICD-10 Disease Import Script
Fetches top-level ICD-10-CM category codes from the NLM Clinical Tables API
and inserts them into MongoDB as a 'diseases' collection.

REVERSIBLE: To undo, simply drop the 'diseases' collection:
    db.diseases.drop()
All documents are tagged with source: "icd10_bulk_import" for easy identification.
"""

import json
import sys
import os
import urllib.request
import urllib.parse
from datetime import datetime

# Add parent dir so we can import app config
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from pymongo import MongoClient
from dotenv import load_dotenv

load_dotenv(os.path.join(os.path.dirname(__file__), '..', '.env'))

MONGODB_URI = os.getenv('MONGODB_URI')
MONGODB_DB_NAME = os.getenv('MONGODB_DB_NAME', 'hipograf')

# ICD-10 chapter -> body region mapping
# More specific sub-ranges override the chapter default
BODY_REGION_MAP = [
    # Infectious diseases
    ("A00", "B99", "whole body"),

    # Neoplasms - mapped by site
    ("C00", "C14", "head/neck"),
    ("C15", "C26", "digestive"),
    ("C30", "C39", "lungs"),
    ("C40", "C41", "bones"),
    ("C43", "C44", "skin"),
    ("C45", "C49", "soft tissue"),
    ("C50", "C50", "breast"),
    ("C51", "C58", "reproductive"),
    ("C60", "C63", "reproductive"),
    ("C64", "C68", "urinary"),
    ("C69", "C72", "brain/nerves"),
    ("C73", "C75", "endocrine"),
    ("C76", "C80", "whole body"),
    ("C81", "C96", "blood/lymph"),
    ("D00", "D09", "whole body"),
    ("D10", "D36", "whole body"),
    ("D37", "D48", "whole body"),
    ("D49", "D49", "whole body"),

    # Blood
    ("D50", "D89", "blood"),

    # Endocrine, nutritional, metabolic
    ("E00", "E07", "thyroid"),
    ("E08", "E13", "pancreas"),
    ("E15", "E16", "pancreas"),
    ("E20", "E35", "endocrine"),
    ("E36", "E36", "endocrine"),
    ("E40", "E46", "whole body"),
    ("E50", "E64", "whole body"),
    ("E65", "E68", "whole body"),
    ("E70", "E88", "whole body"),
    ("E89", "E89", "endocrine"),

    # Mental/behavioral
    ("F01", "F99", "brain"),

    # Nervous system
    ("G00", "G09", "brain"),
    ("G10", "G14", "brain"),
    ("G20", "G26", "brain"),
    ("G30", "G32", "brain"),
    ("G35", "G37", "brain/nerves"),
    ("G40", "G47", "brain"),
    ("G50", "G59", "nerves"),
    ("G60", "G65", "nerves"),
    ("G70", "G73", "muscles/nerves"),
    ("G80", "G83", "brain"),
    ("G89", "G99", "brain/nerves"),

    # Eye
    ("H00", "H59", "eye"),

    # Ear
    ("H60", "H95", "ear"),

    # Circulatory
    ("I00", "I09", "heart"),
    ("I10", "I16", "heart"),
    ("I20", "I25", "heart"),
    ("I26", "I28", "heart/lungs"),
    ("I30", "I52", "heart"),
    ("I60", "I69", "brain"),
    ("I70", "I79", "blood vessels"),
    ("I80", "I89", "blood vessels"),
    ("I95", "I99", "heart"),

    # Respiratory
    ("J00", "J06", "throat/nose"),
    ("J09", "J18", "lungs"),
    ("J20", "J22", "lungs"),
    ("J30", "J39", "throat/nose"),
    ("J40", "J47", "lungs"),
    ("J60", "J70", "lungs"),
    ("J80", "J84", "lungs"),
    ("J85", "J86", "lungs"),
    ("J90", "J94", "lungs"),
    ("J95", "J99", "lungs"),

    # Digestive
    ("K00", "K14", "mouth/teeth"),
    ("K20", "K31", "stomach"),
    ("K35", "K38", "abdomen"),
    ("K40", "K46", "abdomen"),
    ("K50", "K52", "intestines"),
    ("K55", "K64", "intestines"),
    ("K65", "K68", "abdomen"),
    ("K70", "K77", "liver"),
    ("K80", "K87", "gallbladder"),
    ("K90", "K95", "digestive"),

    # Skin
    ("L00", "L99", "skin"),

    # Musculoskeletal
    ("M00", "M02", "joints"),
    ("M04", "M14", "joints"),
    ("M15", "M19", "joints"),
    ("M20", "M25", "joints"),
    ("M26", "M27", "jaw"),
    ("M30", "M36", "soft tissue"),
    ("M40", "M43", "spine"),
    ("M45", "M49", "spine"),
    ("M50", "M54", "spine"),
    ("M60", "M63", "muscles"),
    ("M65", "M67", "tendons"),
    ("M70", "M79", "soft tissue"),
    ("M80", "M85", "bones"),
    ("M86", "M90", "bones"),
    ("M91", "M94", "bones"),
    ("M95", "M99", "musculoskeletal"),

    # Genitourinary
    ("N00", "N08", "kidneys"),
    ("N10", "N16", "kidneys"),
    ("N17", "N19", "kidneys"),
    ("N20", "N23", "kidneys"),
    ("N25", "N29", "kidneys"),
    ("N30", "N39", "bladder/urinary"),
    ("N40", "N53", "reproductive"),
    ("N60", "N65", "breast"),
    ("N70", "N77", "reproductive"),
    ("N80", "N98", "reproductive"),
    ("N99", "N99", "urinary"),

    # Pregnancy
    ("O00", "O9A", "reproductive"),

    # Perinatal
    ("P00", "P96", "whole body"),

    # Congenital
    ("Q00", "Q07", "brain"),
    ("Q10", "Q18", "head/neck"),
    ("Q20", "Q28", "heart"),
    ("Q30", "Q34", "lungs"),
    ("Q35", "Q37", "mouth"),
    ("Q38", "Q45", "digestive"),
    ("Q50", "Q56", "reproductive"),
    ("Q60", "Q64", "urinary"),
    ("Q65", "Q79", "musculoskeletal"),
    ("Q80", "Q89", "whole body"),
    ("Q90", "Q99", "whole body"),

    # Symptoms/signs
    ("R00", "R09", "heart/lungs"),
    ("R10", "R19", "abdomen"),
    ("R20", "R23", "skin/nerves"),
    ("R25", "R29", "musculoskeletal"),
    ("R30", "R39", "urinary"),
    ("R40", "R46", "brain"),
    ("R47", "R49", "throat"),
    ("R50", "R69", "whole body"),
    ("R70", "R79", "blood"),
    ("R80", "R82", "urinary"),
    ("R83", "R89", "whole body"),
    ("R90", "R94", "whole body"),
    ("R97", "R97", "whole body"),
    ("R99", "R99", "whole body"),

    # Injury - by body site
    ("S00", "S09", "head"),
    ("S10", "S19", "neck"),
    ("S20", "S29", "chest"),
    ("S30", "S39", "abdomen"),
    ("S40", "S49", "shoulder/arm"),
    ("S50", "S59", "elbow/forearm"),
    ("S60", "S69", "wrist/hand"),
    ("S70", "S79", "hip/thigh"),
    ("S80", "S89", "knee/leg"),
    ("S90", "S99", "ankle/foot"),
    ("T07", "T88", "whole body"),

    # External causes - skip most, not really diseases
    ("V00", "Y99", "external"),

    # Health services - not diseases
    ("Z00", "Z99", "health services"),
]


def get_body_region(code: str) -> str:
    """Map an ICD-10 code to a body region."""
    code_upper = code.upper().replace(".", "")
    for range_start, range_end, region in BODY_REGION_MAP:
        start = range_start.replace(".", "")
        end = range_end.replace(".", "")
        if start <= code_upper <= end + "ZZZ":
            return region
    return "unspecified"


def fetch_icd10_codes():
    """Fetch ICD-10-CM codes from NLM Clinical Tables API."""
    all_codes = []

    # Search through alphabet + digits to cover all codes
    # The API returns up to 500 results per query
    search_prefixes = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")

    for prefix in search_prefixes:
        print(f"  Fetching codes starting with '{prefix}'...")
        url = (
            f"https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search"
            f"?sf=code,name&terms={prefix}&maxList=500"
        )

        try:
            req = urllib.request.Request(url)
            with urllib.request.urlopen(req, timeout=30) as response:
                data = json.loads(response.read().decode())
                # data[3] contains [[code, name], ...]
                if len(data) > 3 and data[3]:
                    for item in data[3]:
                        code = item[0]
                        name = item[1]
                        all_codes.append((code, name))
        except Exception as e:
            print(f"  Warning: Failed to fetch prefix '{prefix}': {e}")

    return all_codes


def filter_category_codes(all_codes):
    """
    Filter to category-level codes only (3-character codes like 'A00', 'J45').
    These are the ~2000 top-level disease categories.
    """
    seen = set()
    categories = []
    for code, name in all_codes:
        # Category codes are 3 chars (letter + 2 digits)
        cat = code[:3] if len(code) >= 3 else code
        if cat not in seen and len(cat) == 3:
            seen.add(cat)
            # Use the name from the 3-char code if it exists, else first sub-code
            if code == cat or len(code) <= 4:  # exact match or close
                categories.append((cat, name))
            elif cat not in [c for c, _ in categories]:
                categories.append((cat, name))

    # Deduplicate - keep first occurrence
    final = {}
    for code, name in categories:
        if code not in final:
            final[code] = name

    return [(k, v) for k, v in final.items()]


def main():
    print("=" * 60)
    print("ICD-10 Disease Import Script")
    print("=" * 60)

    # Connect to MongoDB
    print(f"\nConnecting to MongoDB...")
    client = MongoClient(MONGODB_URI)
    db = client[MONGODB_DB_NAME]

    # Check if collection already exists with our data
    existing = db.diseases.count_documents({"source": "icd10_bulk_import"})
    if existing > 0:
        print(f"\nFound {existing} existing imported diseases.")
        response = input("Drop and reimport? (y/n): ").strip().lower()
        if response != 'y':
            print("Aborted.")
            return
        db.diseases.delete_many({"source": "icd10_bulk_import"})
        print("Cleared existing imports.")

    # Fetch codes
    print("\nFetching ICD-10-CM codes from NLM API...")
    all_codes = fetch_icd10_codes()
    print(f"  Fetched {len(all_codes)} total codes")

    # Filter to category level
    categories = filter_category_codes(all_codes)
    print(f"  Filtered to {len(categories)} category-level codes")

    # Skip external causes (V-Y) and health services (Z) - not really diseases
    diseases = []
    skipped = 0
    for code, name in sorted(categories):
        region = get_body_region(code)
        if region in ("external", "health services"):
            skipped += 1
            continue

        diseases.append({
            "icdCode": code,
            "name": name,
            "bodyRegion": region,
            "source": "icd10_bulk_import",
            "importedAt": datetime.utcnow().isoformat()
        })

    print(f"  Skipped {skipped} non-disease codes (external causes, health services)")
    print(f"  Ready to insert {len(diseases)} diseases")

    # Insert
    if diseases:
        result = db.diseases.insert_many(diseases)
        print(f"\nInserted {len(result.inserted_ids)} diseases into 'diseases' collection")

    # Print summary by body region
    print("\n" + "=" * 60)
    print("Summary by body region:")
    print("=" * 60)
    region_counts = {}
    for d in diseases:
        r = d["bodyRegion"]
        region_counts[r] = region_counts.get(r, 0) + 1
    for region, count in sorted(region_counts.items(), key=lambda x: -x[1]):
        print(f"  {region:25s} {count:4d}")

    print(f"\n{'TOTAL':25s} {len(diseases):4d}")
    print("\nTo reverse this import, run:")
    print('  db.diseases.drop()')
    print("  or: db.diseases.deleteMany({source: 'icd10_bulk_import'})")

    client.close()


if __name__ == "__main__":
    main()
