#!/usr/bin/env python3
"""
NGON Miner Tool (NMT) - Miner Management Interface
File: /var/www/html/ngon/nmm/nmt.py

This script provides a comprehensive interface for viewing and managing miners.
"""

import pandas as pd
import json
import os
import cgi
import csv
import sys
import logging
import requests
from datetime import datetime
from html import escape

# Add config_manager and auth_manager imports
sys.path.insert(0, '/opt/ngon/apps')
from managers.auth_manager import AuthManager, generate_login_modal_html

# Import universal navigation
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from links import generate_dropdown_html, generate_dropdown_css, generate_dropdown_js


# Google Chat webhook URLs
ACTION_WEBHOOK_URL = "https://chat.googleapis.com/v1/spaces/AAQAiz1ajfU/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=NK9THo1MCd1K6JrTqfUowLO0jdzFT7FGt-JdQA_jp-Q"
ERROR_WEBHOOK_URL = "https://chat.googleapis.com/v1/spaces/AAAAQVmB6NA/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=E5iXxaJncrAmZhFyVCxRDWGzgQE4bEQBpVIpfQEqOnE"
REBOOT_WEBHOOK_URL = "https://chat.googleapis.com/v1/spaces/AAQA_-Rl4hM/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=x6OnjcWTtwO94D3zJ3m0AzMl0a9HjqS6l6y77enSHJ8"

# Sites with only one pod - inventory uses "Site 1" but system uses just "Site"
SINGLE_POD_SITES = ["John"]

def send_to_google_chat(webhook_url, message):
    """Send message to Google Chat."""
    headers = {"Content-Type": "application/json"}
    payload = {"text": message}
    try:
        response = requests.post(webhook_url, headers=headers, json=payload)
        if response.status_code != 200:
            print(f"Error sending to Google Chat: {response.status_code} - {response.text}", file=sys.stderr)
    except requests.RequestException as e:
        print(f"An error occurred while sending to Google Chat: {str(e)}", file=sys.stderr)

class NGONMinerTool:
    """Handles loading and processing of miner data."""
    def __init__(self):
        self.miners_data = []
        self.inventory_data = []
        self.pods = set()
        self.live_pods = set()  # Pods with live miners from miner_status.csv
        self.sleeping_miners = set()
        self.error_codes = {}
        self.miners_file_mtime = None
        self.inventory_file_mtime = None
        self.load_data()

    def load_data(self):
        """Loads data from CSV files into memory."""
        try:
            base_path = '/opt/ngon/data/live/'
            miners_file = os.path.join(base_path, 'miner_status.csv')
            inventory_file = os.path.join(base_path, 'miner_inventory.csv')
            error_codes_file = '/opt/ngon/config/error_codes.csv'

            # Load error codes data
            if os.path.exists(error_codes_file):
                try:
                    df_errors = pd.read_csv(error_codes_file)
                    self.error_codes = {
                        str(row['Error Code']).strip(): {
                            'meaning': str(row['Meaning']).strip(),
                            'solution': str(row['Corresponding Solutions']).strip()
                        } for _, row in df_errors.iterrows()
                    }
                except (FileNotFoundError, pd.errors.EmptyDataError, KeyError):
                    self.error_codes = {}

            # Sleeping miners will be loaded from miner_status.csv below (via mining_state column)
            self.sleeping_miners = set()

            # Load inventory data
            if os.path.exists(inventory_file):
                # Get inventory file modification time
                self.inventory_file_mtime = os.path.getmtime(inventory_file)
                try:
                    df_inventory = pd.read_csv(inventory_file)
                    for _, row in df_inventory.iterrows():
                        # Construct full pod name by combining Site and Pod
                        site = str(row['Site']).strip()
                        pod_num = str(row['Pod']).strip()
                        # Sites with only one pod use just the site name
                        if site in SINGLE_POD_SITES:
                            full_pod = site
                        else:
                            full_pod = f"{site} {pod_num}"

                        inventory_item = {
                            'serial': str(row['Serial']).strip(),
                            'mac': str(row['MAC']).strip(),
                            'pod': full_pod,
                            'position': str(row['Position']).strip(),
                            'site': site
                        }
                        self.inventory_data.append(inventory_item)
                        self.pods.add(full_pod)
                except (FileNotFoundError, pd.errors.EmptyDataError, KeyError):
                    self.inventory_data = []

            # Load miners data
            if os.path.exists(miners_file):
                # Get file modification time
                self.miners_file_mtime = os.path.getmtime(miners_file)
                df_miners = pd.read_csv(miners_file)
                for _, row in df_miners.iterrows():
                    mining_state = str(row['mining_state']).strip().lower() if pd.notna(row.get('mining_state')) else 'idle'
                    miner_data = {
                        'serial': str(row['serial']).strip() if pd.notna(row['serial']) else '',
                        'mac': str(row['mac']).strip() if pd.notna(row['mac']) else '',
                        'ip': str(row['ip']).strip() if pd.notna(row['ip']) else '',
                        'pod': str(row['pod']).strip(),
                        'position': str(row['position']).strip(),
                        'miner_type': str(row['miner_type']).strip(),
                        'mining_state': mining_state,
                        'hs_rt': row['hs_rt'],
                        'power': row['power'],
                        'error_codes': str(row['error_codes']).strip() if pd.notna(row['error_codes']) else '',
                        'uptime': row['uptime'],
                        'is_sleeping': mining_state == 'sleeping',
                        'is_offline': False
                    }
                    
                    self.miners_data.append(miner_data)
                    self.pods.add(miner_data['pod'])
                    self.live_pods.add(miner_data['pod'])  # Track pods with live miners

        except Exception as e:
            print(f"Error loading data: {e}", file=sys.stderr)

    def get_miners_by_pod(self, pod_name):
        """Returns miners for a specific pod, including offline miners from inventory."""
        # Get live miners for this pod
        live_miners = [miner for miner in self.miners_data if miner['pod'] == pod_name]

        # Get live MAC addresses for this pod (normalized to lowercase for case-insensitive comparison)
        live_macs = set(miner['mac'].lower() for miner in live_miners)
        
        # Get inventory miners for this pod that aren't online
        inventory_miners = [item for item in self.inventory_data if item['pod'] == pod_name]
        offline_miners = []
        
        for inv_miner in inventory_miners:
            # Skip empty positions (where BOTH serial AND mac are "nan" or empty)
            serial_empty = (inv_miner['serial'].lower() == 'nan' or inv_miner['serial'] == '')
            mac_empty = (inv_miner['mac'].lower() == 'nan' or inv_miner['mac'] == '')
            
            if serial_empty and mac_empty:
                continue
                
            if inv_miner['mac'].lower() not in live_macs:
                # Create offline miner entry with appropriate default values
                offline_miner = {
                    'serial': inv_miner['serial'],
                    'mac': inv_miner['mac'],
                    'ip': '-',
                    'pod': inv_miner['pod'],
                    'position': inv_miner['position'],
                    'miner_type': 'Unknown',  # We don't have this in inventory
                    'mining_state': 'offline',
                    'hs_rt': '-',
                    'power': '-',
                    'error_codes': 'Offline',
                    'uptime': '-',
                    'is_sleeping': False,
                    'is_offline': True  # Flag to identify offline miners
                }
                offline_miners.append(offline_miner)
        
        # Combine live and offline miners
        all_miners = live_miners + offline_miners
        return all_miners

    def get_unique_pods(self):
        """Returns sorted list of unique pods."""
        return sorted(list(self.pods))

    def get_live_pods(self):
        """Returns sorted list of pods that have live miners (from miner_status.csv)."""
        return sorted(list(self.live_pods))
    
    def get_miners_file_timestamp(self):
        """Returns formatted timestamp and relative time for miners.csv file."""
        if not self.miners_file_mtime:
            return "Unknown"
        
        # Convert to datetime object
        mtime = datetime.fromtimestamp(self.miners_file_mtime)
        now = datetime.now()
        
        # Format the timestamp as MM/DD HH:MM:SS
        formatted_time = mtime.strftime("%m/%d %H:%M:%S")
        
        # Calculate relative time
        time_diff = now - mtime
        total_seconds = int(time_diff.total_seconds())
        
        if total_seconds < 60:
            relative_time = f"({total_seconds}s ago)"
        elif total_seconds < 3600:
            minutes = total_seconds // 60
            relative_time = f"({minutes}m ago)"
        elif total_seconds < 86400:
            hours = total_seconds // 3600
            relative_time = f"({hours}h ago)"
        else:
            days = total_seconds // 86400
            relative_time = f"({days}d ago)"
        
        return f"{formatted_time} {relative_time}"
    
    def get_inventory_file_timestamp(self):
        """Returns formatted timestamp and relative time for inventory.csv file."""
        if not self.inventory_file_mtime:
            return "Unknown"
        
        # Convert to datetime object
        mtime = datetime.fromtimestamp(self.inventory_file_mtime)
        now = datetime.now()
        
        # Format the timestamp as MM/DD HH:MM:SS
        formatted_time = mtime.strftime("%m/%d %H:%M:%S")
        
        # Calculate relative time
        time_diff = now - mtime
        total_seconds = int(time_diff.total_seconds())
        
        if total_seconds < 60:
            relative_time = f"({total_seconds}s ago)"
        elif total_seconds < 3600:
            minutes = total_seconds // 60
            relative_time = f"({minutes}m ago)"
        elif total_seconds < 86400:
            hours = total_seconds // 3600
            relative_time = f"({hours}h ago)"
        else:
            days = total_seconds // 86400
            relative_time = f"({days}d ago)"
        
        return f"{formatted_time} {relative_time}"

def format_uptime(seconds):
    """Converts seconds to dd.hh.mm format."""
    try:
        total_seconds = int(float(seconds))
        days = total_seconds // 86400
        hours = (total_seconds % 86400) // 3600
        minutes = (total_seconds % 3600) // 60
        return f"{days:02d}.{hours:02d}.{minutes:02d}"
    except (ValueError, TypeError):
        return "00.00.00"

def generate_grid_content(miners, error_codes_data):
    """Generates HTML table rows for miners."""
    if not miners:
        return "<tr><td colspan='12' style='text-align: center; color: #666;'>No miners found for selected pod</td></tr>"

    rows_html = []
    for miner in miners:
        error_display = miner['error_codes'] if miner['error_codes'] else 'None'
        uptime_formatted = format_uptime(miner['uptime'])

        # Format hash rate and power for display
        hashrate_formatted = f"{miner['hs_rt']:.2f}" if isinstance(miner['hs_rt'], (int, float)) else miner['hs_rt']
        power_formatted = f"{miner['power']:.2f}" if isinstance(miner['power'], (int, float)) else miner['power']

        # Get mining state
        mining_state = miner.get('mining_state', 'idle')

        # Apply row styling based on state
        row_style = ""
        if mining_state == 'sleeping':
            row_style = 'style="background-color: rgba(74, 158, 255, 0.2);"'

        # Format mode display with color coding
        mode_color = '#4CAF50' if mining_state == 'mining' else '#4a9eff' if mining_state == 'sleeping' else '#888'
        mode_display = mining_state.capitalize()

        # Make error codes clickable if they exist
        error_cell = error_display
        if error_display != 'None':
            error_cell = f'<span onclick="showErrorCodes(\'{escape(miner["mac"])}\', \'{escape(error_display)}\')" style="cursor: pointer; color: #ff6b35;">{escape(error_display)}</span>'

        # Replace empty/missing values with "-" for serial, mac, and position fields
        display_serial = miner['serial'] if miner['serial'] and str(miner['serial']).strip() not in ['', 'nan', 'None'] else '-'
        display_mac = miner['mac'] if miner['mac'] and str(miner['mac']).strip() not in ['', 'nan', 'None'] else '-'
        display_position = miner['position'] if miner['position'] and str(miner['position']).strip() not in ['', 'nan', 'None'] else '-'

        rows_html.append(f"""
            <tr {row_style} data-mode="{escape(mining_state)}">
                <td style="text-align: center;"><input type="checkbox" class="miner-checkbox" data-mac="{escape(miner['mac'])}" data-ip="{escape(miner['ip'])}"></td>
                <td data-sort="{escape(miner['pod'])}">{escape(miner['pod'])}</td>
                <td data-sort="{escape(display_position)}">{escape(display_position)}</td>
                <td data-sort="{escape(miner['miner_type'])}" onclick="showMinerInfo('{escape(miner['mac'])}')" style="cursor: pointer; text-decoration: underline; color: #4CAF50;">{escape(miner['miner_type'])}</td>
                <td data-sort="{escape(mining_state)}" style="color: {mode_color};">{mode_display}</td>
                <td data-sort="{miner['hs_rt']}">{hashrate_formatted}</td>
                <td data-sort="{'zzz' if error_display == 'None' else error_display}">{error_cell}</td>
                <td data-sort="{miner['power']}">{power_formatted}</td>
                <td data-sort="{miner['uptime']}">{uptime_formatted}</td>
                <td data-sort="{escape(display_serial)}">{escape(display_serial)}</td>
                <td data-sort="{escape(display_mac)}">{escape(display_mac)}</td>
                <td data-sort="{escape(miner['ip'])}">{escape(miner['ip'])}</td>
            </tr>
        """)

    return ''.join(rows_html)

def handle_sleeping_csv_update(form):
    """Handle sleep/wake operations by forwarding to powercontrol API."""
    try:
        action = form.getvalue('action', '')
        miners_json = form.getvalue('miners', '')
        
        if not action or not miners_json:
            print(f"Missing data - action: {action}, miners: {miners_json}", file=sys.stderr)
            return False
            
        miners = json.loads(miners_json)
        
        # Extract IP addresses (IP-only, no MAC fallback)
        ips = [miner['ip'] for miner in miners]
        
        # For wake operations, check if miners are actually sleeping (for logging purposes)
        sleeping_ips_for_reduction = []
        if action == 'wake':
            # Load currently sleeping miners from miner_status.csv
            currently_sleeping = set()
            try:
                miner_status_file = '/opt/ngon/data/live/miner_status.csv'
                if os.path.exists(miner_status_file) and os.path.getsize(miner_status_file) > 0:
                    with open(miner_status_file, 'r') as f:
                        reader = csv.DictReader(f)
                        for row in reader:
                            mining_state = row.get('mining_state', '').strip().lower()
                            if mining_state == 'sleeping':
                                ip = row.get('ip', '').strip()
                                if ip:
                                    currently_sleeping.add(ip)

                # Check how many of the selected miners are actually sleeping
                sleeping_ips_for_reduction = [ip for ip in ips if ip in currently_sleeping]

                print(f"NMT Wake: {len(sleeping_ips_for_reduction)} of {len(ips)} miners are currently sleeping", file=sys.stderr)

            except Exception as e:
                print(f"Error checking sleeping miners: {e}", file=sys.stderr)

        # Prepare payload for powercontrol API
        payload = {
            "identifiers": ips,
            "user": "NMT",
            "reason": "NMT"
        }
        
        # Forward to miner API
        if action == 'sleep':
            endpoint = "http://127.0.0.1:5004/stop_mining"
        elif action == 'wake':
            endpoint = "http://127.0.0.1:5004/resume_mining"
        else:
            print(f"Unknown action: {action}", file=sys.stderr)
            return False
        
        response = requests.post(
            endpoint,
            json=payload,
            timeout=300
        )
        
        if response.status_code == 200:
            return {"success": True, "reason": "NMT"}
        else:
            error_msg = f"Miner API returned {response.status_code}: {response.text}"
            print(error_msg, file=sys.stderr)
            send_to_google_chat(ERROR_WEBHOOK_URL, f"Error in NMT {action}: {error_msg}")
            return {"success": False, "error": error_msg}
        
    except Exception as e:
        error_msg = f"Error in NMT {action} operation: {str(e)}"
        print(error_msg, file=sys.stderr)
        
        # Send error alert to Google Chat
        send_to_google_chat(ERROR_WEBHOOK_URL, error_msg)
        
        return {"success": False, "error": error_msg}

def handle_reboot_logging(form):
    """Handle reboot operations by forwarding to powercontrol API."""
    try:
        miners_json = form.getvalue('miners', '')
        
        if not miners_json:
            print(f"Missing miners data for reboot", file=sys.stderr)
            return False
            
        miners = json.loads(miners_json)
        
        # Extract IP addresses (IP-only, no MAC fallback)
        ips = [miner['ip'] for miner in miners]
        time_minutes = miners[0].get('time_minutes') if miners else None
        allow_sleeping = bool(miners[0].get('allow_sleeping')) if miners else False

        # Prepare payload for miner_actions API format
        payload = {
            "identifiers": ips,
            "action": "reboot",
            "user": "NMT",
            "reason": "NMT",
            "allow_sleeping": allow_sleeping
        }
        if time_minutes is not None:
            payload["stagger_minutes"] = time_minutes
        
        # Forward to miner API
        response = requests.post(
            "http://127.0.0.1:5004/reboot",
            json=payload,
            timeout=300
        )
        
        if response.status_code == 200:
            return True
        else:
            error_msg = f"Miner API returned {response.status_code}: {response.text}"
            print(error_msg, file=sys.stderr)
            send_to_google_chat(ERROR_WEBHOOK_URL, f"Error in NMT reboot: {error_msg}")
            return False
        
    except Exception as e:
        error_msg = f"Error in NMT reboot operation: {str(e)}"
        print(error_msg, file=sys.stderr)
        send_to_google_chat(ERROR_WEBHOOK_URL, error_msg)
        return False


def handle_get_info(form):
    """Handle get_info operations by forwarding to miner API."""
    try:
        macs_json = form.getvalue('macs', '')
        
        if not macs_json:
            return {"error": "Missing macs data for get_info"}
            
        # The 'macs' parameter can now contain IPs as well
        macs = json.loads(macs_json)
        
        # Forward to miner API
        response = requests.post(
            "http://127.0.0.1:5004/get_info",
            json={"identifiers": macs},
            timeout=300
        )
        
        if response.status_code == 200:
            data = response.json()
            # Convert miner_api format to expected format for frontend
            converted_results = []

            for i, result in enumerate(data.get("results", [])):
                ip = result.get("ip", "Unknown")
                # Use original MAC from request (API doesn't return MAC)
                mac = macs[i] if i < len(macs) else "Unknown"

                # The API returns data directly in result, not nested under 'info'
                # Pass the full result as the info for the frontend
                converted_result = {
                    "mac": mac,
                    "ip": ip,
                    "miner_type": result.get("miner_type", "Unknown"),
                    "response": json.dumps(result),  # Full result for frontend
                    "success": result.get("success", True),
                    "info": result  # Include raw info for frontend access
                }
                converted_results.append(converted_result)

            converted_data = {
                "status": "success" if data.get("successful", 0) > 0 else "error",
                "status_code": 200,
                "results": converted_results
            }
            return converted_data
        else:
            error_msg = f"Miner API returned {response.status_code}: {response.text}"
            print(error_msg, file=sys.stderr)
            return {"error": error_msg}
        
    except Exception as e:
        error_msg = f"Error in NMT get_info operation: {str(e)}"
        print(error_msg, file=sys.stderr)
        return {"error": error_msg}

def generate_pod_checkboxes(pods, selected_pods):
    """Generate HTML for pod checkboxes."""
    html = []
    # Add Clear All option at the top
    html.append('<label style="background: rgba(255,255,255,0.05); font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.1);"><a href="#" onclick="clearAllPods(); return false;" style="color: #ff6b35; text-decoration: none;">✕ Clear All</a></label>')
    
    for pod in pods:
        checked = 'checked' if pod in selected_pods else ''
        escaped_pod = escape(pod)
        html.append(f'<label><input type="checkbox" value="{escaped_pod}" {checked} onchange="updateSelectedPods()"> {escaped_pod}</label>')
    return '\n'.join(html)

def load_sleep_targets():
    """Load sleep targets - deprecated, returns empty dict."""
    return {}

def get_generator_status(gen_data):
    """Determine if a generator is online, offline, or stuck.

    Matches the logic from status_data.js:
    - Running + fresh data (< 30 min) = online
    - Running + stale data (> 30 min) = stuck (excluded from capacity)
    - Not Running = offline
    """
    run_status = gen_data.get('status', 'Unknown')

    # Check if generator is stuck (Running status but stale data)
    if run_status == 'Running':
        stats = gen_data.get('stats', {})
        last_update = stats.get('last_update')
        if last_update:
            try:
                from datetime import datetime, timezone

                last_update_str = str(last_update)

                # Parse ISO format with timezone (e.g., 2026-01-16T05:34:35-07:00)
                if 'T' in last_update_str:
                    # Python 3.7+ fromisoformat handles timezone
                    update_time = datetime.fromisoformat(last_update_str)
                    # If no timezone info, assume UTC
                    if update_time.tzinfo is None:
                        update_time = update_time.replace(tzinfo=timezone.utc)
                else:
                    # Simple format without timezone
                    update_time = datetime.strptime(last_update_str, '%Y-%m-%d %H:%M:%S')
                    update_time = update_time.replace(tzinfo=timezone.utc)

                # Compare using UTC
                now = datetime.now(timezone.utc)
                hours_since_update = (now - update_time).total_seconds() / 3600

                # If data is older than 30 minutes, consider it stuck
                if hours_since_update > 0.5:
                    return 'stuck'
            except Exception:
                pass  # If we can't parse, assume it's fine

        return 'online'

    return 'offline'

def fetch_status_and_calculate_capacity():
    """Fetch status API data and calculate capacity per generator group/pod.

    Returns dict with:
    - pod_sleeping: {pod_name: sleeping_count}
    - pod_capacity: {pod_name: {'available_kw': float, 'miner_capacity': int, 'gen_group': str}}
    """
    result = {
        'pod_sleeping': {},
        'pod_capacity': {},
        'miner_types': {}
    }

    try:
        # Fetch status API data
        response = requests.get('http://localhost:5050/api/status', timeout=10)
        if response.status_code != 200:
            print(f"Status API returned {response.status_code}", file=sys.stderr)
            return result

        status_data = response.json()

        # Get miner types configuration
        miner_types = status_data.get('misc', {}).get('miner_types', {})
        result['miner_types'] = miner_types

        # Process each site and generator group
        sites = status_data.get('sites', {})

        for site_name, site_data in sites.items():
            # Skip non-operational sites
            if site_name in ['Out of Service', 'Spares']:
                continue

            generator_groups = site_data.get('generator_groups', {})

            for group_name, group_data in generator_groups.items():
                generators = group_data.get('generators', {})
                pods = group_data.get('pods', {})

                if not generators or not pods:
                    continue

                # Calculate capacity for this generator group
                running_gens = 0
                current_power_output = 0.0

                for gen_id, gen_data in generators.items():
                    status = get_generator_status(gen_data)
                    if status == 'online':  # online = running and not stuck
                        running_gens += 1
                        stats = gen_data.get('stats', {})
                        power_avg = float(stats.get('power_avg', 0) or 0)
                        current_power_output += power_avg

                # Calculate available capacity (320kW per running gen - current output)
                max_capacity_kw = running_gens * 320
                available_kw = max_capacity_kw - current_power_output

                # Find predominant miner type in this group's pods
                miner_type_counts = {}
                for pod_name, pod_data in pods.items():
                    miner_type = pod_data.get('miner_type', 'M60')
                    miner_type_counts[miner_type] = miner_type_counts.get(miner_type, 0) + 1

                # Get most common miner type
                if miner_type_counts:
                    predominant_type = max(miner_type_counts, key=miner_type_counts.get)
                else:
                    predominant_type = 'M60'

                # Get spec wattage for this miner type
                spec_wattage = miner_types.get(predominant_type, {}).get('spec_wattage', 3400)

                # Calculate miner capacity
                if available_kw > 0:
                    miner_capacity = int((available_kw * 1000) / spec_wattage)
                else:
                    miner_capacity = 0

                # Store capacity for each pod in this group
                for pod_name, pod_data in pods.items():
                    # Get sleeping count from pod stats
                    stats = pod_data.get('stats', {})
                    sleeping_count = int(stats.get('miners_sleeping', 0) or 0)

                    result['pod_sleeping'][pod_name] = sleeping_count
                    result['pod_capacity'][pod_name] = {
                        'available_kw': round(available_kw, 1),
                        'miner_capacity': miner_capacity,
                        'gen_group': group_name,
                        'running_gens': running_gens,
                        'miner_type': pod_data.get('miner_type', 'Unknown')
                    }

    except Exception as e:
        print(f"Error fetching status/calculating capacity: {e}", file=sys.stderr)
        import traceback
        traceback.print_exc(file=sys.stderr)

    return result

def get_all_pods_from_config():
    """Get all pods from config manager (master_config.json)."""
    try:
        from managers.config_manager import config
        
        # Get all pod names from the config
        pod_counts = config.get_pod_miner_counts()
        return sorted(list(pod_counts.keys()))
        
    except Exception as e:
        print(f"Error loading pods from config: {e}", file=sys.stderr)
        # Fallback to empty list if config fails
        return []

def handle_update_sleep_target(form):
    """Handle updating sleep target - deprecated, no longer used."""
    return {"success": False, "error": "Sleep targets are deprecated"}

def generate_sleep_targets_html(sleep_targets, all_pods):
    """Generate HTML for sleep targets - deprecated, returns empty string."""
    return '<div style="color: #888; padding: 10px;">Sleep targets are no longer used.</div>'

def generate_capacity_section_html(capacity_data):
    """Generate HTML for the capacity management section.

    Two sections:
    1. Current Sleeping - pods with sleeping miners, showing count and gen group capacity
    2. Available Capacity - gen groups sorted by largest capacity first
    """
    pod_sleeping = capacity_data.get('pod_sleeping', {})
    pod_capacity = capacity_data.get('pod_capacity', {})

    html_parts = []

    # Section 1: Current Sleeping (by pod, since sleeping is per-pod)
    html_parts.append('''
        <div class="capacity-section">
            <h3 style="color: #4a9eff; margin-bottom: 10px; font-size: 14px;">Current Sleeping</h3>
            <div class="capacity-list" id="sleepingList">
    ''')

    # Get pods with sleeping miners, sorted by sleeping count (descending)
    sleeping_pods = [(pod, count) for pod, count in pod_sleeping.items() if count > 0]
    sleeping_pods.sort(key=lambda x: x[1], reverse=True)

    if sleeping_pods:
        for pod, sleeping_count in sleeping_pods:
            cap_info = pod_capacity.get(pod, {})
            miner_capacity = cap_info.get('miner_capacity', 0)
            gen_group = cap_info.get('gen_group', '')

            # Color code capacity: green if positive, gray if zero/negative
            cap_color = '#4CAF50' if miner_capacity > 0 else '#888'

            html_parts.append(f'''
                <div class="capacity-row">
                    <span class="pod-name" style="flex: 1;">{escape(pod)}</span>
                    <span class="sleeping-count" style="color: #4a9eff; width: 40px; text-align: right;">{sleeping_count}</span>
                    <span class="miner-capacity" style="color: {cap_color}; width: 60px; text-align: right;">{miner_capacity}</span>
                </div>
            ''')
    else:
        html_parts.append('<div style="color: #888; font-style: italic;">No sleeping miners</div>')

    html_parts.append('''
            </div>
        </div>
    ''')

    # Section 2: Available Capacity (by gen group to avoid duplicates)
    html_parts.append('''
        <div class="capacity-section" style="margin-top: 20px;">
            <h3 style="color: #4CAF50; margin-bottom: 10px; font-size: 14px;">Available Capacity</h3>
            <div class="capacity-list" id="capacityList">
    ''')

    # Group by gen_group to avoid showing duplicate capacity for pods sharing gens
    gen_group_capacity = {}
    for pod, info in pod_capacity.items():
        gen_group = info.get('gen_group', pod)  # Fallback to pod name if no gen_group
        miner_capacity = info.get('miner_capacity', 0)
        available_kw = info.get('available_kw', 0)

        # Only add if this gen_group hasn't been added yet (avoid duplicates)
        if gen_group not in gen_group_capacity:
            gen_group_capacity[gen_group] = {
                'miner_capacity': miner_capacity,
                'available_kw': available_kw
            }

    # Filter to only groups with capacity > 0 and sort by capacity (descending)
    capacity_groups = [
        (group, info) for group, info in gen_group_capacity.items()
        if info.get('miner_capacity', 0) > 0
    ]
    capacity_groups.sort(key=lambda x: x[1].get('miner_capacity', 0), reverse=True)

    if capacity_groups:
        for gen_group, info in capacity_groups:
            miner_capacity = info.get('miner_capacity', 0)

            html_parts.append(f'''
                <div class="capacity-row">
                    <span class="pod-name" style="flex: 1;">{escape(gen_group)}</span>
                    <span class="miner-capacity" style="color: #4CAF50; width: 80px; text-align: right;">{miner_capacity} miners</span>
                </div>
            ''')
    else:
        html_parts.append('<div style="color: #888; font-style: italic;">No available capacity</div>')

    html_parts.append('''
            </div>
        </div>
    ''')

    return '\n'.join(html_parts)

def main():
    """Main function to handle CGI requests and render the page."""
    # Handle POST requests for sleeping CSV updates and reboot logging
    form = cgi.FieldStorage()
    request_method = os.environ.get('REQUEST_METHOD', 'GET')
    
    # Authentication
    auth = AuthManager('nmt')
    auth_required, should_exit, headers = auth.require_auth(form)

    if should_exit:
        if request_method == 'POST':
            print("Content-Type: application/json")
        else:
            print("Content-Type: text/html")
        if headers:
            print(headers)
        print("")
        if auth_required:
            if request_method == 'POST':
                print(json.dumps({"success": False, "error": "Authentication required"}))
        else:
            print(json.dumps({"success": True}))
        return

    if auth_required:
        from managers.auth_manager import generate_login_page_html
        print("Content-Type: text/html\n")
        print(generate_login_page_html("NMT (NGON Miner Tool)"))
        return
    
    if request_method == 'POST' and form.getvalue('action') in ['sleep', 'wake']:
        print("Content-Type: application/json\n")
        result = handle_sleeping_csv_update(form)
        if isinstance(result, dict):
            print(json.dumps(result))
        else:
            # Legacy boolean return (backwards compatibility)
            print(json.dumps({"success": result}))
        return
    elif request_method == 'POST' and form.getvalue('action') == 'reboot':
        print("Content-Type: application/json\n")
        success = handle_reboot_logging(form)
        print(json.dumps({"success": success}))
        return
    elif request_method == 'POST' and form.getvalue('action') == 'get_info':
        print("Content-Type: application/json\n")
        result = handle_get_info(form)
        print(json.dumps(result))
        return
    elif request_method == 'POST' and form.getvalue('action') == 'update_sleep_target':
        print("Content-Type: application/json\n")
        result = handle_update_sleep_target(form)
        print(json.dumps(result))
        return
    elif request_method == 'POST' and form.getvalue('action') == 'get_sleep_targets':
        print("Content-Type: application/json\n")
        try:
            targets = load_sleep_targets()
            print(json.dumps({"success": True, "targets": targets}))
        except Exception as e:
            print(json.dumps({"success": False, "error": str(e)}))
        return
    
    print("Content-Type: text/html\n")
    
    # Initialize the tool
    tool = NGONMinerTool()
    
    # Handle form data
    selected_pods_str = form.getvalue('pods', '')
    selected_pods = selected_pods_str.split(',') if selected_pods_str else []
    only_sleeping = form.getvalue('only_sleeping', '')
    only_zero = form.getvalue('only_zero', '')
    only_offline = form.getvalue('only_offline', '')
    
    # Get miners for selected pods
    miners = []
    if selected_pods:
        for pod in selected_pods:
            if pod:  # Skip empty strings
                miners.extend(tool.get_miners_by_pod(pod))
    # If no pods selected, show empty table - user must select pods to avoid loading huge datasets
    
    # Generate grid content
    grid_content = generate_grid_content(miners, tool.error_codes)
    
    # Only show pods that have live miners (from miner_status.csv)
    pods = tool.get_live_pods()

    # Fetch capacity data from status API
    capacity_data = fetch_status_and_calculate_capacity()

    # Generate capacity section HTML
    capacity_section_html = generate_capacity_section_html(capacity_data)
    
    # Create a JSON representation of selected pods for JavaScript
    selected_pods_json = json.dumps(selected_pods)
    
    # Prepare error codes for JavaScript
    error_codes_json = json.dumps(tool.error_codes)
    
    # Render the complete HTML page
    html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NGON Miner Tool</title>
    <link rel="stylesheet" href="ngon-modals.css">
    <link rel="stylesheet" href="nmt.css">
    <link rel="stylesheet" href="/shared/miner_view.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
    <script src="/shared/miner_view.js"></script>
    <style>
        /* Universal header styling to match other pages */
        body {{
            padding: 20px !important;
            background: #1a1a1a !important;
        }}
        
        .container {{
            max-width: none !important;
        }}
        
        .header {{
            background-color: rgba(255,255,255,0.05) !important;
            border-radius: 8px !important;
            padding: 30px !important;
            margin-bottom: 30px !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
            flex-wrap: wrap !important;
            gap: 20px !important;
        }}
        
        .header h1.dropdown-title {{
            font-size: 2.2em !important;
            font-weight: 600 !important;
            margin: 0 !important;
            color: #00ff00 !important;
        }}
        
        {generate_dropdown_css()}

        @keyframes spin {{
            0% {{ transform: rotate(0deg); }}
            100% {{ transform: rotate(360deg); }}
        }}
    </style>
    <script>
        // Template variables that need to be available to nmt.js
        window.errorCodesData = {error_codes_json};
        window.selectedPods = {selected_pods_json};
        window.allPods = {json.dumps(pods)};
        window.dataTimestamp = {tool.miners_file_mtime if tool.miners_file_mtime else 'null'};
        window.inventoryTimestamp = {tool.inventory_file_mtime if tool.inventory_file_mtime else 'null'};
    </script>
    <script src="nmt.js"></script>
</head>
<body>
    <div class="header">
        <div>
            <div class="dropdown">
                <h1 class="dropdown-title">NGON Mining - NMT</h1>
                <div class="dropdown-content">{generate_dropdown_html(AuthManager.get_user_access())}</div>
            </div>
            <div style="display: flex; flex-direction: column; gap: 5px; margin-top: 10px;">
                <div id="dataTimestamp" style="color: #ccc; font-size: 0.9rem;">
                    <strong>Miner Data Updated:</strong> {tool.get_miners_file_timestamp()}
                </div>
                <div id="inventoryTimestamp" style="color: #ccc; font-size: 0.9rem;">
                    <strong>Inventory Updated:</strong> {tool.get_inventory_file_timestamp()}
                </div>
                <div id="visibleCount" style="color: #ccc; font-size: 0.9rem;">
                    <strong>Count:</strong> <span id="countNumber">0</span>
                </div>
                <div id="selectedCount" style="color: #ccc; font-size: 0.9rem;">
                    <strong>Selected:</strong> <span id="selectedNumber">0</span>
                </div>
            </div>
        </div>
        <div id="sparklineContainer" style="flex: 1; min-width: 300px; display: flex; align-items: center; justify-content: flex-end; padding-right: 40px;">
            <div id="sparklineWrapper" style="width: 100%; max-width: 1600px; height: 165px; display: none; padding: 8px;">
                <canvas id="sparklineChart"></canvas>
            </div>
            <div id="sparklineLoading" style="display: none; color: #888; font-size: 0.9rem;">
                <span style="display: inline-block; width: 12px; height: 12px; border: 2px solid #333; border-top: 2px solid #00ff00; border-radius: 50%; animation: spin 1s linear infinite; margin-right: 8px;"></span>
                Loading hashrate...
            </div>
        </div>
    </div>
    
    <div class="container">
        <div class="page-wrapper">
            <div class="left-content">
                <div class="controls-header">
                    <div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 20px;">
                        <div class="header-controls">
                            <form id="minerForm" method="post" style="display: contents;">
                                <input type="hidden" name="pods" id="selectedPodsInput" value="{selected_pods_str}">
                                <input type="hidden" name="only_sleeping" id="onlySleepingHidden" value="{only_sleeping}">
                                <input type="hidden" name="only_zero" id="onlyZeroHidden" value="{only_zero}">
                                <input type="hidden" name="only_offline" id="onlyOfflineHidden" value="{only_offline}">
                                <div class="control-group">
                                    <label class="control-label">Online Pods:</label>
                                    <div class="multiselect">
                                        <div class="selectBox" onclick="showCheckboxes()">
                                            <span id="selectBoxText">{', '.join(selected_pods) if selected_pods else 'Select Pods...'}</span>
                                        </div>
                                        <div id="checkboxes" class="checkboxes">
                                            {generate_pod_checkboxes(pods, selected_pods)}
                                        </div>
                                    </div>
                                </div>
                            </form>
                            <div class="control-group">
                                <label class="control-label">With Selected:</label>
                                <div style="display: flex; gap: 10px;">
                                    <button onclick="rebootSelected()" style="background: rgba(229, 62, 62, 0.2); border-color: rgba(229, 62, 62, 0.4);">Reboot</button>
                                    <button onclick="sleepSelected()" style="background: rgba(255, 152, 0, 0.2); border-color: rgba(255, 152, 0, 0.4);">Sleep</button>
                                    <button onclick="wakeSelected()" style="background: rgba(72, 187, 120, 0.2); border-color: rgba(72, 187, 120, 0.4);">Wake</button>
                                </div>
                            </div>
                            <div class="control-group">
                                <label class="control-label">Actions:</label>
                                <div style="display: flex; gap: 10px;">
                                    <button onclick="refreshInventory()">Refresh</button>
                                    <button onclick="exportData()">Export</button>
                                    <button onclick="showPullList()">Pull List</button>
                                </div>
                            </div>
                            <div class="control-group">
                                <label class="control-label">Filters:</label>
                                <div style="display: flex; flex-direction: column; gap: 5px;">
                                    <!-- Positive filters (Only) -->
                                    <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
                                        <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem; cursor: pointer;">
                                            <input type="checkbox" id="onlySleepingFilter" onchange="applyFilters()" style="margin: 0;">
                                            Only Sleeping
                                        </label>
                                        <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem; cursor: pointer;">
                                            <input type="checkbox" id="onlyZeroFilter" onchange="applyFilters()" style="margin: 0;">
                                            Only Zero
                                        </label>
                                        <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem; cursor: pointer;">
                                            <input type="checkbox" id="onlyOfflineFilter" onchange="applyFilters()" style="margin: 0;">
                                            Only Offline
                                        </label>
                                    </div>
                                    <!-- Negative filters (Not) -->
                                    <div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
                                        <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem; cursor: pointer;">
                                            <input type="checkbox" id="notSleepingFilter" onchange="applyFilters()" style="margin: 0;">
                                            Not Sleeping&nbsp;&nbsp;
                                        </label>
                                        <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem; cursor: pointer;">
                                            <input type="checkbox" id="notZeroFilter" onchange="applyFilters()" style="margin: 0;">
                                            Not Zero&nbsp;&nbsp;
                                        </label>
                                        <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem; cursor: pointer;">
                                            <input type="checkbox" id="notOfflineFilter" onchange="applyFilters()" style="margin: 0;">
                                            Not Offline
                                        </label>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <!-- Search box - separate row below controls -->
                        <div style="margin-top: 2px; text-align: right; padding-right: 10px; width: 100%;">
                            <label style="color: #ccc; margin-right: 10px; font-size: 0.9rem;">Search:</label>
                            <input type="text" id="searchInput" placeholder="Search MAC or Serial..." 
                                   onkeyup="applyFilters()" 
                                   style="padding: 5px; border: 1px solid #444; background: #222; color: #fff; border-radius: 3px; width: 250px;">
                        </div>
                    </div>
                </div>
                
                <div class="main-content">
                <div class="grid-container">
                    <table id="minerTable">
                        <thead>
                            <tr>
                                <th style="text-align: center; width: 40px;"><input type="checkbox" id="selectAll" onchange="toggleAllCheckboxes()"></th>
                                <th onclick="sortTable(1)" style="cursor: pointer; width: 60px;">Pod ↕</th>
                                <th onclick="sortTable(2)" style="cursor: pointer; width: 60px;">Position ↕</th>
                                <th onclick="sortTable(3)" style="cursor: pointer; width: 90px;">Miner Type ↕</th>
                                <th onclick="sortTable(4)" style="cursor: pointer; width: 70px;">Mode ↕</th>
                                <th onclick="sortTable(5)" style="cursor: pointer; width: 80px;">Hash Rate ↕</th>
                                <th onclick="sortTable(6)" style="cursor: pointer; width: 250px;">Error Codes ↕</th>
                                <th onclick="sortTable(7)" style="cursor: pointer; width: 60px;">Power ↕</th>
                                <th onclick="sortTable(8)" style="cursor: pointer; width: 60px;">Uptime ↕</th>
                                <th onclick="sortTable(9)" style="cursor: pointer; width: 200px;">Serial ↕</th>
                                <th onclick="sortTable(10)" style="cursor: pointer; width: 120px;">MAC Address ↕</th>
                                <th onclick="sortTable(11)" style="cursor: pointer; width: 80px;">IP Address ↕</th>
                            </tr>
                        </thead>
                        <tbody id="minerTableBody" class="loading" style="visibility: hidden; opacity: 0; transition: opacity 0.3s ease, visibility 0.3s ease;">
                            {grid_content}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
        <div class="sleep-targets-section">
            <h2 style="color: #00ff00;">Capacity Overview</h2>
            <div id="capacityContainer">
                {capacity_section_html}
            </div>
        </div>
    </div>

    <script>
        // Mobile dropdown functionality
        document.addEventListener('DOMContentLoaded', function() {{
            const dropdownTitle = document.querySelector('.dropdown-title');
            const dropdownContent = document.querySelector('.dropdown-content');
            
            if (dropdownTitle && dropdownContent) {{
                dropdownTitle.addEventListener('click', function(e) {{
                    if (window.innerWidth <= 768) {{
                        e.preventDefault();
                        dropdownContent.classList.toggle('show');
                    }}
                }});
                
                // Close dropdown when clicking outside
                document.addEventListener('click', function(e) {{
                    if (!e.target.closest('.dropdown') && dropdownContent.classList.contains('show')) {{
                        dropdownContent.classList.remove('show');
                    }}
                }});
                
                // Close dropdown when window is resized to desktop
                window.addEventListener('resize', function() {{
                    if (window.innerWidth > 768) {{
                        dropdownContent.classList.remove('show');
                    }}
                }});
            }}
        }});
        
        // Add dropdown navigation JavaScript
        {generate_dropdown_js()}
    </script>
</body>
</html>"""
    
    print(html_content)

if __name__ == "__main__":
    main()
