#!/usr/bin/env python3
"""
Pinterest Batch Scheduler - Schedule entire week's pins in one run
Runs Monday morning, schedules 3 listings/day for Mon-Sun (21 listings total)
Balanced approach: ~21 pins/day stays in Pinterest's optimal 15-25 engagement zone
"""

import json
import sys
import requests
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Optional, List
import logging
import time

# Add parent to path for config
sys.path.insert(0, str(Path(__file__).parent))
from config import *

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(LOG_DIR / "pinterest-batch.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# Scheduling constants
LISTINGS_PER_DAY = 3  # Balanced approach: 3 × ~7 images = ~21 pins/day (optimal 15-25 range)
DAYS_TO_SCHEDULE = 7
TIME_SLOTS = ["09:00", "14:00", "19:00"]  # PST - 3 slots for 3 listings
MAX_PINS_PER_DAY = 25  # Safety margin, staying in optimal engagement zone


def get_headers() -> dict:
    """Get Publer API headers"""
    return {
        "Authorization": f"Bearer-API {PUBLER_API_KEY}",
        "Publer-Workspace-Id": PUBLER_WORKSPACE_ID,
        "Content-Type": "application/json"
    }


def load_listings() -> dict:
    """Load listings JSON"""
    if not LISTINGS_JSON.exists():
        logger.error(f"Listings file not found: {LISTINGS_JSON}")
        sys.exit(1)
    
    with open(LISTINGS_JSON, 'r') as f:
        return json.load(f)


def save_listings(data: dict):
    """Save listings JSON"""
    with open(LISTINGS_JSON, 'w') as f:
        json.dump(data, f, indent=2)


def find_archive_folder(listing: dict) -> Optional[Path]:
    """Find archive folder matching a listing by title keywords."""
    title = listing.get("title", "").lower()
    
    exclude_folders = ["listed", "rejected", "source-pngs", "cleanup-20260210"]
    archive_folders = [
        f for f in ARCHIVE_DIR.iterdir() 
        if f.is_dir() and f.name not in exclude_folders
    ]
    
    title_words = []
    for word in title.lower().replace(",", "").replace("-", " ").split():
        if len(word) > 3 and word not in ["with", "for", "the", "and", "svg", "cut", "file", "cricut", "silhouette"]:
            title_words.append(word)
    title_words = title_words[:4]
    
    best_match = None
    best_score = 0
    
    for folder in archive_folders:
        folder_name = folder.name.lower().replace("_", " ").replace("-", " ")
        score = sum(1 for w in title_words if w in folder_name)
        if score > best_score:
            best_score = score
            best_match = folder
    
    if best_score >= 2:
        return best_match
    return None


def get_images_to_post(folder: Path) -> list[Path]:
    """Get all JPGs from folder, excluding whats-included"""
    images = []
    for img in folder.glob("*.jpg"):
        if img.name.lower() not in [e.lower() for e in EXCLUDED_FILES]:
            images.append(img)
    return sorted(images)


def get_unposted_listings(data: dict, max_count: int = 35) -> List[dict]:
    """Get unposted listings, priority first, up to max_count."""
    listings = data.get("listings", [])
    unposted = []
    
    # Priority listings first
    for listing in listings:
        if len(unposted) >= max_count:
            break
        if listing.get("priority", False) and not listing.get("delisted"):
            posted = set(listing.get("posted_images", []))
            scheduled = set(listing.get("scheduled_images", []))
            folder = find_archive_folder(listing)
            if folder:
                images = get_images_to_post(folder)
                remaining = [img for img in images if img.name not in posted and img.name not in scheduled]
                if remaining:
                    unposted.append(listing)
    
    # Then non-priority listings
    for listing in listings:
        if len(unposted) >= max_count:
            break
        if not listing.get("priority", False) and not listing.get("delisted"):
            posted = set(listing.get("posted_images", []))
            scheduled = set(listing.get("scheduled_images", []))
            folder = find_archive_folder(listing)
            if folder:
                images = get_images_to_post(folder)
                remaining = [img for img in images if img.name not in posted and img.name not in scheduled]
                if remaining:
                    unposted.append(listing)
    
    return unposted


def upload_media(image_path: Path) -> Optional[dict]:
    """Upload image to Publer Media Library."""
    headers = {
        "Authorization": f"Bearer-API {PUBLER_API_KEY}",
        "Publer-Workspace-Id": PUBLER_WORKSPACE_ID
    }
    
    try:
        with open(image_path, 'rb') as f:
            files = {'file': (image_path.name, f, 'image/jpeg')}
            resp = requests.post(
                f"{PUBLER_API_BASE}/media",
                headers=headers,
                files=files,
                data={'direct_upload': 'true', 'in_library': 'false'}
            )
            resp.raise_for_status()
            result = resp.json()
            return {
                "id": result.get("id") or result.get("_id"),
                "path": result.get("path", ""),
                "thumbnail": result.get("thumbnail", "")
            }
    except Exception as e:
        logger.error(f"Failed to upload media {image_path.name}: {e}")
        return None


def poll_job_status(job_id: str, max_attempts: int = 30) -> Optional[dict]:
    """Poll job status until completed or failed."""
    headers = get_headers()
    
    for attempt in range(max_attempts):
        try:
            resp = requests.get(
                f"{PUBLER_API_BASE}/job_status/{job_id}",
                headers=headers
            )
            resp.raise_for_status()
            result = resp.json()
            
            if isinstance(result, list):
                result = result[0] if result else {}
            
            status = result.get("status") if isinstance(result, dict) else None
            if status in ["completed", "complete"]:
                return result
            elif status == "failed":
                logger.error(f"Job failed: {result}")
                return None
            
            time.sleep(1)
        except Exception as e:
            logger.error(f"Error polling job status: {e}")
            time.sleep(1)
    
    return None


def schedule_pinterest_pin(
    media_info: dict,
    title: str,
    description: str,
    link: str,
    board_id: str,
    scheduled_at: str  # ISO 8601 format
) -> bool:
    """Schedule a Pinterest pin for future publishing."""
    headers = get_headers()
    
    payload = {
        "bulk": {
            "state": "scheduled",
            "posts": [
                {
                    "networks": {
                        "pinterest": {
                            "type": "photo",
                            "text": description[:500],
                            "title": title[:100],
                            "url": link,
                            "media": [
                                {
                                    "id": media_info["id"],
                                    "type": "photo",
                                    "path": media_info.get("path", ""),
                                    "thumbnail": media_info.get("thumbnail", "")
                                }
                            ]
                        }
                    },
                    "accounts": [
                        {
                            "id": PINTEREST_ACCOUNT_ID,
                            "album_id": board_id,
                            "scheduled_at": scheduled_at
                        }
                    ]
                }
            ]
        }
    }
    
    try:
        resp = requests.post(
            f"{PUBLER_API_BASE}/posts/schedule",
            headers=headers,
            json=payload
        )
        resp.raise_for_status()
        result = resp.json()
        
        job_id = result.get("job_id")
        if job_id:
            final = poll_job_status(job_id)
            if final:
                return True
        return False
            
    except Exception as e:
        logger.error(f"Failed to schedule pin: {e}")
        if hasattr(e, 'response') and e.response is not None:
            logger.error(f"Response: {e.response.text[:500]}")
        return False


def generate_description(listing: dict) -> str:
    """Generate Pinterest-optimized description"""
    title = listing.get("title", "")
    category = listing.get("category", "")
    niche = CATEGORY_TO_NICHE.get(category, "christian")
    
    niche_tags = {
        "military": "#USMC #MarineCorps #VeteranMade #MilitaryLife #SemperFi",
        "patriotic": "#USA #PatrioticDecor #AmericanMade #4thOfJuly #RedWhiteBlue",
        "christian": "#ChristianArt #FaithBased #ChurchDecor #ReformedTheology #Easter",
        "outdoor": "#HuntingLife #WildlifeArt #CabinDecor #OutdoorLife #NatureArt"
    }
    
    description = f"""✨ {title} ✨

Perfect for Cricut, Silhouette, laser cutters & vinyl cutting machines!

🎨 Instant digital download includes:
• SVG file (vector, infinitely scalable)
• PNG file (high resolution, transparent background)

💡 Create stunning:
• T-shirts & apparel
• Tumblers & mugs  
• Wall art & signs
• Car decals & stickers

{niche_tags.get(niche, niche_tags['christian'])} #SVGCutFile #DigitalDownload #CricutMade
"""
    return description.strip()


def get_schedule_times(start_date: datetime, day_offset: int, time_slots: List[str]) -> List[datetime]:
    """Generate schedule times for a specific day."""
    target_date = start_date + timedelta(days=day_offset)
    times = []
    
    for slot in time_slots:
        hour, minute = map(int, slot.split(":"))
        scheduled = target_date.replace(hour=hour, minute=minute, second=0, microsecond=0)
        times.append(scheduled)
    
    return times


def schedule_listing(listing: dict, schedule_time: datetime, data: dict) -> int:
    """Schedule all images from a listing for a specific time. Returns count."""
    folder = find_archive_folder(listing)
    if not folder:
        logger.warning(f"No archive folder found for: {listing.get('title', 'Unknown')[:50]}")
        return 0
    
    posted = set(listing.get("posted_images", []))
    scheduled = set(listing.get("scheduled_images", []))
    images = get_images_to_post(folder)
    remaining = [img for img in images if img.name not in posted and img.name not in scheduled]
    
    if not remaining:
        return 0
    
    description = listing.get("pinterest_description") or generate_description(listing)
    category = listing.get("category", "")
    board_id = PINTEREST_BOARD_IDS.get(category, PINTEREST_BOARD_IDS["Easter SVGs"])
    
    # Convert to ISO format with timezone
    scheduled_at = schedule_time.strftime("%Y-%m-%dT%H:%M:%S") + "-08:00"  # PST
    
    scheduled_count = 0
    for img in remaining:
        logger.info(f"Scheduling: {img.name} for {scheduled_at}")
        
        media_info = upload_media(img)
        if not media_info:
            continue
        
        success = schedule_pinterest_pin(
            media_info=media_info,
            title=listing.get("title", ""),
            description=description,
            link=listing.get("url", ""),
            board_id=board_id,
            scheduled_at=scheduled_at
        )
        
        if success:
            if "scheduled_images" not in listing:
                listing["scheduled_images"] = []
            listing["scheduled_images"].append(img.name)
            listing["last_scheduled"] = datetime.now().isoformat()
            scheduled_count += 1
            save_listings(data)
            time.sleep(1)  # Rate limit protection
    
    return scheduled_count


def main():
    """Main entry point - schedule entire week's pins"""
    logger.info("=" * 60)
    logger.info("Pinterest Batch Scheduler - Starting Weekly Schedule")
    logger.info("=" * 60)
    
    data = load_listings()
    
    # Get listings to schedule (up to 35 for the week)
    max_listings = LISTINGS_PER_DAY * DAYS_TO_SCHEDULE
    listings = get_unposted_listings(data, max_listings)
    
    if not listings:
        logger.info("No unposted listings found")
        print("ALL_SCHEDULED")
        return
    
    logger.info(f"Found {len(listings)} listings to schedule")
    
    # Start scheduling from today
    start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    
    total_scheduled = 0
    listing_index = 0
    
    for day in range(DAYS_TO_SCHEDULE):
        if listing_index >= len(listings):
            break
            
        day_pins = 0
        schedule_times = get_schedule_times(start_date, day, TIME_SLOTS)
        day_date = (start_date + timedelta(days=day)).strftime("%Y-%m-%d")
        
        logger.info(f"\n--- Day {day + 1}: {day_date} ---")
        
        # Schedule up to 5 listings per day (one per time slot)
        for slot_idx, schedule_time in enumerate(schedule_times):
            if listing_index >= len(listings):
                break
            if day_pins >= MAX_PINS_PER_DAY:
                logger.warning(f"Hit daily pin limit for {day_date}")
                break
                
            listing = listings[listing_index]
            logger.info(f"Scheduling: {listing.get('title', '')[:50]} at {schedule_time.strftime('%H:%M')}")
            
            scheduled = schedule_listing(listing, schedule_time, data)
            day_pins += scheduled
            total_scheduled += scheduled
            listing_index += 1
            
            logger.info(f"Scheduled {scheduled} pins for this listing")
    
    logger.info("=" * 60)
    logger.info(f"COMPLETE: Scheduled {total_scheduled} total pins for {listing_index} listings")
    logger.info("=" * 60)
    
    print(f"SUCCESS: Scheduled {total_scheduled} pins for {listing_index} listings over {DAYS_TO_SCHEDULE} days")


if __name__ == "__main__":
    main()
