#!/usr/bin/env python3
"""
MyMind Smart Importer for Notion
- Parses MyMind CSV export
- Scrapes OpenGraph data for visual covers
- Deduplicates against existing Notion items
- Normalizes and consolidates tags
- Imports to Notion Inspiration database with covers
"""

import csv
import json
import re
import sys
import time
from datetime import datetime
from pathlib import Path
from urllib.parse import urlparse

import requests
from bs4 import BeautifulSoup

# Configuration
NOTION_API_KEY_PATH = "/home/clawd/secrets/notion/api_key"
NOTION_DB_ID = "2ff330c2-8646-81f0-bbd9-ec474393d7a5"
SCRAPE_DELAY = 0.8  # seconds between URL fetches
NOTION_DELAY = 0.35  # seconds between Notion API calls
REQUEST_TIMEOUT = 10  # seconds

# Tags to skip (not useful for library)
SKIP_TAGS = {"read later", "watch later", "read-later", "watch-later"}

# Tag normalization map (consolidate similar tags)
TAG_NORMALIZE = {
    "branding": "Branding",
    "brand identity": "Brand Identity",
    "brandidentity": "Brand Identity",
    "design": "Design",
    "graphic design": "Graphic Design",
    "graphicdesign": "Graphic Design",
    "web design": "Web Design",
    "webdesign": "Web Design",
    "ui/ux": "UI/UX",
    "ux/ui": "UI/UX",
    "ui design": "UI/UX",
    "ux design": "UI/UX",
    "typography": "Typography",
    "packaging": "Packaging",
    "packaging design": "Packaging",
    "illustration": "Illustration",
    "photography": "Photography",
    "animation": "Animation",
    "motion": "Motion Design",
    "motion design": "Motion Design",
    "motion graphics": "Motion Design",
    "3d": "3D",
    "3d design": "3D",
    "art direction": "Art Direction",
    "artdirection": "Art Direction",
    "creative": "Creative",
    "creativity": "Creative",
    "ai": "AI",
    "artificial intelligence": "AI",
    "generative ai": "AI",
    "midjourney": "Midjourney",
    "food": "Food & Drink",
    "food & drink": "Food & Drink",
    "food photography": "Food & Drink",
    "hospitality": "Hospitality",
    "architecture": "Architecture",
    "interior design": "Interior Design",
    "product design": "Product Design",
    "identity": "Identity",
    "visual identity": "Visual Identity",
    "logo": "Logo Design",
    "logo design": "Logo Design",
    "portfolio": "Portfolio",
    "website": "Website",
    "web development": "Web Development",
    "figma": "Figma",
    "fonts": "Typography",
    "typeface": "Typography",
}

# User agent for scraping
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
}


def load_notion_key():
    """Load Notion API key from file."""
    with open(NOTION_API_KEY_PATH, "r") as f:
        return f.read().strip()


def normalize_tag(tag: str) -> str:
    """Normalize a tag to consistent format."""
    tag = tag.strip()
    tag_lower = tag.lower()
    
    # Skip certain tags
    if tag_lower in SKIP_TAGS:
        return ""
    
    # Apply normalization map
    if tag_lower in TAG_NORMALIZE:
        return TAG_NORMALIZE[tag_lower]
    
    # Title case if not in map
    return tag.title()


def process_tags(tags_str: str, max_tags: int = 10) -> list[str]:
    """Process comma-separated tags string into normalized list."""
    if not tags_str:
        return []
    
    tags = [normalize_tag(t) for t in tags_str.split(",")]
    tags = [t for t in tags if t]  # Remove empty
    tags = list(dict.fromkeys(tags))  # Remove duplicates, preserve order
    return tags[:max_tags]


def scrape_opengraph(url: str) -> dict:
    """Scrape OpenGraph metadata from a URL."""
    result = {
        "og_image": None,
        "og_title": None,
        "og_description": None,
        "scraped": False,
        "error": None,
    }
    
    if not url or not url.startswith(("http://", "https://")):
        result["error"] = "Invalid URL"
        return result
    
    try:
        response = requests.get(
            url,
            headers=HEADERS,
            timeout=REQUEST_TIMEOUT,
            allow_redirects=True,
        )
        response.raise_for_status()
        
        soup = BeautifulSoup(response.content, "html.parser")
        
        # Extract OpenGraph tags
        og_image = soup.find("meta", property="og:image")
        og_title = soup.find("meta", property="og:title")
        og_description = soup.find("meta", property="og:description")
        
        # Fallback to twitter cards
        if not og_image:
            og_image = soup.find("meta", attrs={"name": "twitter:image"})
        if not og_title:
            og_title = soup.find("meta", attrs={"name": "twitter:title"})
        if not og_description:
            og_description = soup.find("meta", attrs={"name": "twitter:description"})
        
        # Extract content
        if og_image and og_image.get("content"):
            img_url = og_image["content"]
            # Make relative URLs absolute
            if img_url.startswith("/"):
                parsed = urlparse(url)
                img_url = f"{parsed.scheme}://{parsed.netloc}{img_url}"
            result["og_image"] = img_url
        
        if og_title and og_title.get("content"):
            result["og_title"] = og_title["content"].strip()
        
        if og_description and og_description.get("content"):
            result["og_description"] = og_description["content"].strip()[:500]
        
        result["scraped"] = True
        
    except requests.Timeout:
        result["error"] = "Timeout"
    except requests.RequestException as e:
        result["error"] = str(e)[:100]
    except Exception as e:
        result["error"] = f"Parse error: {str(e)[:80]}"
    
    return result


def get_existing_urls(api_key: str) -> set[str]:
    """Fetch existing URLs from Notion database for deduplication."""
    existing = set()
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Notion-Version": "2022-06-28",
        "Content-Type": "application/json",
    }
    
    url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
    has_more = True
    start_cursor = None
    
    print("Fetching existing Notion items for deduplication...")
    
    while has_more:
        payload = {"page_size": 100}
        if start_cursor:
            payload["start_cursor"] = start_cursor
        
        try:
            response = requests.post(url, headers=headers, json=payload, timeout=30)
            response.raise_for_status()
            data = response.json()
            
            for page in data.get("results", []):
                props = page.get("properties", {})
                # Try to find URL property
                for prop_name in ["Link", "URL", "url", "link"]:
                    if prop_name in props:
                        prop = props[prop_name]
                        if prop.get("type") == "url" and prop.get("url"):
                            existing.add(prop["url"])
                            break
            
            has_more = data.get("has_more", False)
            start_cursor = data.get("next_cursor")
            time.sleep(NOTION_DELAY)
            
        except Exception as e:
            print(f"Error fetching existing items: {e}")
            break
    
    print(f"Found {len(existing)} existing URLs in Notion")
    return existing


def create_notion_page(api_key: str, item: dict, og_data: dict) -> dict:
    """Create a Notion page for an item."""
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Notion-Version": "2022-06-28",
        "Content-Type": "application/json",
    }
    
    # Determine title
    title = item.get("title", "").strip()
    if not title and og_data.get("og_title"):
        title = og_data["og_title"]
    if not title:
        title = "Untitled"
    
    # Build properties
    properties = {
        "Name": {
            "title": [{"text": {"content": title[:2000]}}]
        },
    }
    
    # Add URL if present
    if item.get("url"):
        properties["Link"] = {"url": item["url"]}
    
    # Add Source Board
    properties["Source Board"] = {
        "select": {"name": "MyMind"}
    }
    
    # Add Tags (multi-select)
    tags = process_tags(item.get("tags", ""))
    if tags:
        properties["Tags"] = {
            "multi_select": [{"name": tag} for tag in tags]
        }
    
    # Build page payload
    page = {
        "parent": {"database_id": NOTION_DB_ID},
        "properties": properties,
    }
    
    # Add cover image if available
    if og_data.get("og_image"):
        page["cover"] = {
            "type": "external",
            "external": {"url": og_data["og_image"]}
        }
    
    # Create page
    try:
        response = requests.post(
            "https://api.notion.com/v1/pages",
            headers=headers,
            json=page,
            timeout=30,
        )
        
        if response.status_code == 200:
            return {"success": True, "id": response.json().get("id")}
        else:
            error = response.json().get("message", response.text[:200])
            return {"success": False, "error": error}
            
    except Exception as e:
        return {"success": False, "error": str(e)[:200]}


def parse_csv(csv_path: str) -> list[dict]:
    """Parse MyMind CSV export."""
    items = []
    with open(csv_path, "r", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        for row in reader:
            items.append(row)
    return items


def main():
    if len(sys.argv) < 2:
        print("Usage: python mymind_smart_import.py <csv_path>")
        sys.exit(1)
    
    csv_path = sys.argv[1]
    
    # Load items
    print(f"Loading CSV from {csv_path}...")
    items = parse_csv(csv_path)
    print(f"Found {len(items)} items in CSV")
    
    # Filter to items with URLs (web content)
    web_items = [i for i in items if i.get("url") and i["url"].startswith("http")]
    local_items = [i for i in items if not i.get("url") or not i["url"].startswith("http")]
    
    print(f"Web items (with URLs): {len(web_items)}")
    print(f"Local items (images/notes): {len(local_items)}")
    
    # Load Notion API key
    api_key = load_notion_key()
    
    # Get existing URLs for deduplication
    existing_urls = get_existing_urls(api_key)
    
    # Filter out duplicates
    new_items = [i for i in web_items if i.get("url") not in existing_urls]
    skipped_dupes = len(web_items) - len(new_items)
    print(f"New items to import: {len(new_items)} (skipping {skipped_dupes} duplicates)")
    
    if not new_items:
        print("No new items to import!")
        return
    
    # Process items
    stats = {
        "success": 0,
        "failed": 0,
        "scraped": 0,
        "scrape_failed": 0,
        "with_cover": 0,
    }
    
    results = []
    
    for i, item in enumerate(new_items):
        url = item.get("url", "")
        title = item.get("title", "")[:50] or url[:50]
        
        print(f"\n[{i+1}/{len(new_items)}] {title}...")
        
        # Scrape OpenGraph data
        og_data = scrape_opengraph(url)
        
        if og_data["scraped"]:
            stats["scraped"] += 1
            if og_data["og_image"]:
                stats["with_cover"] += 1
                print(f"  ✓ Found cover image")
            else:
                print(f"  - No cover image found")
        else:
            stats["scrape_failed"] += 1
            print(f"  ✗ Scrape failed: {og_data.get('error', 'unknown')}")
        
        time.sleep(SCRAPE_DELAY)
        
        # Create Notion page
        result = create_notion_page(api_key, item, og_data)
        
        if result["success"]:
            stats["success"] += 1
            print(f"  ✓ Created in Notion")
        else:
            stats["failed"] += 1
            print(f"  ✗ Notion error: {result.get('error', 'unknown')}")
        
        results.append({
            "url": url,
            "title": item.get("title"),
            "og_data": og_data,
            "notion_result": result,
        })
        
        time.sleep(NOTION_DELAY)
    
    # Summary
    print("\n" + "="*50)
    print("IMPORT COMPLETE")
    print("="*50)
    print(f"Successfully imported: {stats['success']}")
    print(f"Failed to import: {stats['failed']}")
    print(f"URLs scraped: {stats['scraped']}")
    print(f"Scrape failures: {stats['scrape_failed']}")
    print(f"Items with covers: {stats['with_cover']}")
    print(f"Duplicates skipped: {skipped_dupes}")
    print(f"Local items skipped: {len(local_items)}")
    
    # Save results
    results_path = Path(csv_path).parent / "mymind_import_results.json"
    with open(results_path, "w") as f:
        json.dump({
            "stats": stats,
            "results": results,
            "skipped_local": [i.get("title") or i.get("id") for i in local_items],
        }, f, indent=2)
    print(f"\nResults saved to: {results_path}")


if __name__ == "__main__":
    main()
