#!/usr/bin/env python3
"""
ffplayout Schedule Generator for New Ellijay TV
Scans media folders, builds a 24h playlist with dayparts, commercials & bumpers.
"""

import os
import json
import glob
import random
import subprocess
import datetime
from pathlib import Path

# ========== CONFIGURATION ==========
MEDIA_ROOT = "/var/lib/ffplayout/tv-media/Shuffle-for-tv"
PLAYLIST_OUTPUT_DIR = "/var/lib/ffplayout/playlists"   # where ffplayout expects daily JSON
COMMERCIALS_DIR = os.path.join(MEDIA_ROOT, "Commercials")
BUMPERS_DIR = os.path.join(COMMERCIALS_DIR, "bumpers")  # you have bumpers inside Commercials
WEATHER_FILE = "/var/lib/ffplayout/tv-media/weather/weather_report.mp4"

# Daypart definitions: (start_time, end_time, category_folders, shuffle)
# Times are in seconds from midnight.
DAYPARTS = [
    # Morning: cartoons & kids shows (6:00 – 12:00)
    (6*3600, 12*3600, ["carntoons", "silent/kids"], True),
    # Afternoon: classic TV & vintage sitcoms (12:00 – 18:00)
    (12*3600, 18*3600, ["Classic TV", "Vintage sitcoms"], True),
    # Evening: prime time movies & NETV originals (18:00 – 23:00)
    (18*3600, 23*3600, ["Movies", "NETV Originals", "syndicated"], True),
    # Late night: horror / sci‑fi / weird (23:00 – 02:00)
    (23*3600, 26*3600, ["Movies/Horror", "Movies/Sci-Fi", "silent"], True),
    # Overnight: music, concerts, experimental (02:00 – 06:00)
    (2*3600, 6*3600, ["Music", "Other"], True),
]

# Insert a commercial break every N minutes (in seconds)
COMMERCIAL_INTERVAL = 15 * 60   # every 15 minutes
# Each commercial break lasts this many seconds (approx)
COMMERCIAL_BLOCK_DURATION = 60   # 1 minute of commercials/bumpers

# ========== HELPER FUNCTIONS ==========
def get_video_duration(filepath):
    """Return duration in seconds using ffprobe."""
    cmd = [
        'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
        '-of', 'default=noprint_wrappers=1:nokey=1', filepath
    ]
    try:
        output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True).strip()
        return float(output)
    except:
        print(f"Warning: Could not get duration for {filepath}")
        return 30.0   # fallback

def collect_media_from_folders(folder_list, base_dir):
    """Recursively find all .mp4 (and .mkv, .avi) files under given folders."""
    media_files = []
    extensions = ('.mp4', '.mkv', '.avi', '.mov', '.m4v')
    for folder in folder_list:
        full_path = os.path.join(base_dir, folder)
        if not os.path.exists(full_path):
            print(f"Warning: folder {full_path} does not exist, skipping")
            continue
        for ext in extensions:
            for filepath in glob.glob(f"{full_path}/**/*{ext}", recursive=True):
                media_files.append(filepath)
    return media_files

def load_commercials_and_bumpers():
    """Return list of all commercial & bumper video files."""
    commercials = []
    if os.path.exists(COMMERCIALS_DIR):
        for ext in ('.mp4', '.mkv'):
            commercials.extend(glob.glob(f"{COMMERCIALS_DIR}/**/*{ext}", recursive=True))
    if os.path.exists(BUMPERS_DIR):
        for ext in ('.mp4', '.mkv'):
            commercials.extend(glob.glob(f"{BUMPERS_DIR}/**/*{ext}", recursive=True))
    # Remove duplicates and ensure they exist
    commercials = list(set(commercials))
    return commercials

def build_program_entry(source_file, start_time):
    """Create a playlist entry for ffplayout."""
    duration = get_video_duration(source_file)
    return {
        "in": 0,
        "out": duration,
        "duration": duration,
        "source": source_file,
        "start": start_time   # will be replaced later; used for sorting
    }

def schedule_daypart(daypart, start_sec, end_sec, all_media, commercials, bumpers):
    """Fill a time block with shuffled media and intersperse commercials."""
    schedule = []
    current_time = start_sec
    # Shuffle media for this daypart
    random.shuffle(all_media)
    
    for media_file in all_media:
        if current_time >= end_sec:
            break
        # Add commercial break every COMMERCIAL_INTERVAL (but not at very start)
        if current_time > start_sec and (current_time - start_sec) % COMMERCIAL_INTERVAL < 10:
            # Insert a short commercial block
            comm_block = []
            remaining = COMMERCIAL_BLOCK_DURATION
            while remaining > 0 and commercials:
                comm = random.choice(commercials)
                comm_dur = get_video_duration(comm)
                if comm_dur > remaining:
                    # optionally trim, but we'll just skip
                    continue
                comm_block.append(build_program_entry(comm, current_time))
                current_time += comm_dur
                remaining -= comm_dur
            schedule.extend(comm_block)
        
        # Add the actual program
        prog = build_program_entry(media_file, current_time)
        schedule.append(prog)
        current_time += prog["duration"]
    
    # If we didn't reach end_sec, fill with bumpers or loop media
    while current_time < end_sec:
        filler = random.choice(commercials + bumpers)
        filler_dur = get_video_duration(filler)
        if current_time + filler_dur > end_sec:
            break
        schedule.append(build_program_entry(filler, current_time))
        current_time += filler_dur
    
    return schedule

# ========== MAIN ==========
def main():
    # Create output directory if missing
    os.makedirs(PLAYLIST_OUTPUT_DIR, exist_ok=True)
    
    # Load all commercials and bumpers once
    commercials = load_commercials_and_bumpers()
    if not commercials:
        print("Warning: No commercials/bumpers found, continuing without them")
    
    # Load weather report (optional) – we'll insert once per day, e.g., at noon
    weather = None
    if os.path.exists(WEATHER_FILE):
        weather = WEATHER_FILE
    
    # Build the full 24h schedule
    full_schedule = []
    # Sort dayparts by start time
    sorted_dayparts = sorted(DAYPARTS, key=lambda x: x[0])
    
    for start, end, folders, shuffle_flag in sorted_dayparts:
        # Collect all media from the given folders (recursive)
        media_list = collect_media_from_folders(folders, MEDIA_ROOT)
        if not media_list:
            print(f"Warning: No media found for daypart {folders}")
            continue
        if shuffle_flag:
            random.shuffle(media_list)
        # Get the schedule for this block (commercials inserted automatically)
        block_schedule = schedule_daypart(
            (start, end), start, end, media_list, commercials, commercials
        )
        full_schedule.extend(block_schedule)
    
    # Sort the entire schedule by start time (ffplayout requires increasing start)
    full_schedule.sort(key=lambda x: x["start"])
    
    # Inject weather report at noon (if available)
    if weather:
        noon_ts = 12*3600
        # Find insertion point
        insert_idx = 0
        for i, prog in enumerate(full_schedule):
            if prog["start"] >= noon_ts:
                insert_idx = i
                break
        weather_entry = build_program_entry(weather, noon_ts)
        full_schedule.insert(insert_idx, weather_entry)
        # Shift all later entries by weather duration
        weather_dur = weather_entry["duration"]
        for i in range(insert_idx+1, len(full_schedule)):
            full_schedule[i]["start"] += weather_dur
    
    # Build final JSON playlist
    today = datetime.date.today()
    playlist = {
        "channel": "New Ellijay TV",
        "date": today.isoformat(),
        "program": full_schedule
    }
    
    output_file = os.path.join(PLAYLIST_OUTPUT_DIR, f"{today.isoformat()}.json")
    with open(output_file, 'w') as f:
        json.dump(playlist, f, indent=2)
    
    print(f"Playlist generated: {output_file}")
    print(f"Total programs: {len(full_schedule)}")
    print("Next: restart ffplayout or wait for it to load the new day's playlist")

if __name__ == "__main__":
    main()
