#!/usr/bin/env python3
import cgi
import sys
sys.path.insert(0, '/opt/ngon/apps')
from managers.auth_manager import AuthManager, generate_login_page_html

_form = cgi.FieldStorage()
_auth = AuthManager('zero_hash')
_auth_required, _should_exit, _headers = _auth.require_auth(_form)

if _should_exit:
    print("Content-Type: application/json")
    if _headers:
        print(_headers)
    print("")
    if _auth_required:
        print('{"success": false, "error": "Authentication required"}')
    else:
        print('{"success": true}')
    sys.exit(0)

if _auth_required:
    print("Content-Type: text/html")
    print("")
    print(generate_login_page_html("Zero Hash Analysis"))
    sys.exit(0)


"""
Zero Hashrate Analysis Tool
File: /var/www/html/ngon/status/zero_hash.py

Comprehensive analysis of zero hashrate miners with historical trending
and detailed miner information for troubleshooting idle mode issues.
"""

import pandas as pd
import json
import os
import sys
import cgi
import time
from html import escape
from datetime import datetime, timezone, timedelta

# Import the config manager
sys.path.insert(0, '/opt/ngon/apps')
from managers.config_manager import config

# InfluxDB imports
import influxdb_client
from influxdb_client import InfluxDBClient

class ZeroHashrateAnalyzer:
    """Handles loading and processing of zero hashrate data."""
    def __init__(self, load_miner_details=True):
        self.inventory_data = {}
        self.sleeping_miners = set()
        self.error_codes = {}
        self.zero_hash_miners = []
        
        # Initialize InfluxDB client
        self.init_influxdb()
        self.load_data(load_miner_details)

    def init_influxdb(self):
        """Initialize InfluxDB client for historical data."""
        try:
            # InfluxDB settings
            token = 'rehPqmCSlCkQnEAaFQEX75JD7J9lAsflaIS6TTYnIh3SetVyzEumX_dRMizxFEsEISG4VUdTd70rUHcJig7V6w=='
            org = "ngonsolutions"
            url = "https://us-central1-1.gcp.cloud2.influxdata.com"
            
            # New simplified buckets
            self.zero_hash_bucket = 'MARAGON_Zero_Hash'
            self.sleeping_bucket = 'MARAGON_Sleeping'
            self.offline_bucket = 'MARAGON_Offline'
            self.online_hashing_bucket = 'MARAGON_Online_Hashing'
            self.router_status_bucket = 'MARAGON_Router_Status'
            
            self.influx_client = InfluxDBClient(url=url, token=token, org=org)
            self.influx_query_api = self.influx_client.query_api()
        except Exception as e:
            self.influx_client = None
            self.influx_query_api = None

    def load_data(self, load_miner_details=True):
        """Load current miner data from CSV files."""
        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
            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:
                    self.error_codes = {}

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

            # Load inventory data - only if needed for miner details
            if load_miner_details and os.path.exists(inventory_file):
                try:
                    df_inventory = pd.read_csv(inventory_file)
                    for _, row in df_inventory.iterrows():
                        site = str(row['Site']).strip()
                        pod_num = str(row['Pod']).strip()
                        full_pod = f"{site} {pod_num}"
                        
                        mac = str(row['MAC']).strip().upper()
                        serial = str(row['Serial']).strip()
                        position = str(row['Position']).strip()
                        
                        if mac != 'NAN' and serial != 'NAN':
                            self.inventory_data[mac] = {
                                'pod': full_pod,
                                'position': position,
                                'serial': serial
                            }
                except:
                    pass

            # Load current miner status - only if needed for performance
            if load_miner_details and os.path.exists(miners_file):
                try:
                    df_miners = pd.read_csv(miners_file)

                    # First, populate sleeping miners from mining_state column
                    if 'mining_state' in df_miners.columns:
                        sleeping_df = df_miners[df_miners['mining_state'].str.lower() == 'sleeping']
                        self.sleeping_miners = set(sleeping_df['mac'].astype(str).str.strip().str.upper())

                    for _, row in df_miners.iterrows():
                        mac = str(row['mac']).strip().upper()
                        hashrate = float(row.get('hs_rt', 0)) if row.get('hs_rt') not in [None, '', 'nan'] else 0
                        
                        # Only include zero hashrate miners that are not sleeping
                        if hashrate == 0 and mac not in self.sleeping_miners:
                            miner_data = {
                                'mac': mac,
                                'pod': str(row.get('pod', '')).strip(),
                                'ip': str(row.get('ip', '')).strip(),
                                'error_codes': str(row.get('error_codes', '')).strip(),
                                'uptime': row.get('uptime', 0),
                                'hashrate': hashrate,
                                'is_sleeping': False
                            }
                            
                            # Add inventory data if available, use "-" for missing data
                            try:
                                if mac and mac != 'NAN' and mac in self.inventory_data:
                                    # Only update position and serial from inventory, keep CSV pod name
                                    inventory_info = self.inventory_data[mac]
                                    miner_data.update({
                                        'position': inventory_info.get('position', '-'),
                                        'serial': inventory_info.get('serial', '-')
                                    })
                                else:
                                    miner_data.update({
                                        'position': '-',
                                        'serial': '-'
                                    })
                            except Exception:
                                miner_data.update({
                                    'position': '-',
                                    'serial': '-'
                                })
                            
                            # Get miner model from config manager, use "-" if fails
                            try:
                                miner_type = config.get_miner_model_by_ip(miner_data['ip']) if miner_data['ip'] else None
                                miner_data['miner_type'] = miner_type if miner_type else '-'
                            except Exception:
                                miner_data['miner_type'] = '-'
                                
                            self.zero_hash_miners.append(miner_data)
                            
                except Exception as e:
                    print(f"Error processing miners: {str(e)}", file=sys.stderr)

        except Exception as e:
            pass

    def get_latest_influx_count(self, pod_filter=None):
        """Get current zero hash, sleeping, and offline counts from new simplified buckets."""
        if not self.influx_query_api:
            return None, None, None
        
        try:
            # Build pod filter for queries
            pod_filter_clause = ""
            if pod_filter and pod_filter != "All Pods":
                pod_filter_clause = f'|> filter(fn: (r) => r._measurement == "{pod_filter}")'
            
            # Query zero hash count - get most recent values for all pods, then sum
            zero_query = f'''
from(bucket: "{self.zero_hash_bucket}")
  |> range(start: -30m)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> group(columns: ["_measurement"])
  |> last()
  |> group()
  |> sum()
            '''
            
            # Query sleeping count  
            sleeping_query = f'''
from(bucket: "{self.sleeping_bucket}")
  |> range(start: -30m)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> group(columns: ["_measurement"])
  |> last()
  |> group()
  |> sum()
            '''
            
            # Query offline count
            offline_query = f'''
from(bucket: "{self.offline_bucket}")
  |> range(start: -30m)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> group(columns: ["_measurement"])
  |> last()
  |> group()
  |> sum()
            '''
            
            # Execute queries
            zero_result = self.influx_query_api.query(org="ngonsolutions", query=zero_query)
            sleeping_result = self.influx_query_api.query(org="ngonsolutions", query=sleeping_query)
            offline_result = self.influx_query_api.query(org="ngonsolutions", query=offline_query)
            
            # Extract counts
            zero_count = 0
            for table in zero_result:
                for record in table.records:
                    zero_count = int(record.get_value() or 0)
                    break
            
            sleeping_count = 0
            for table in sleeping_result:
                for record in table.records:
                    sleeping_count = int(record.get_value() or 0)
                    break
            
            offline_count = 0
            for table in offline_result:
                for record in table.records:
                    offline_count = int(record.get_value() or 0)
                    break
            
            return zero_count, sleeping_count, offline_count
            
        except Exception as e:
            print(f"InfluxDB latest count query error: {str(e)}", file=sys.stderr)
            return None, None, None

    def get_csv_sleeping_count(self, pod_filter=None):
        """Get current sleeping count from miner_status.csv for comparison."""
        try:
            if pod_filter and pod_filter != "All Pods":
                # Load sleeping miners with pod information from miner_status.csv
                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:
                    import pandas as pd
                    df_miners = pd.read_csv(miner_status_file)
                    if not df_miners.empty and 'mining_state' in df_miners.columns:
                        sleeping_df = df_miners[df_miners['mining_state'].str.lower() == 'sleeping']
                        if 'pod' in sleeping_df.columns:
                            pod_sleeping = sleeping_df[sleeping_df['pod'] == pod_filter]
                            return len(pod_sleeping)
                return 0
            else:
                return len(self.sleeping_miners)
        except Exception:
            return 0

    def get_csv_zero_count(self, pod_filter=None):
        """Get current zero hashrate count from CSV (online miners only)."""
        try:
            # If zero_hash_miners is already populated, use it
            if self.zero_hash_miners:
                if pod_filter and pod_filter != "All Pods":
                    pod_zero_miners = [m for m in self.zero_hash_miners if m.get('pod') == pod_filter]
                    return len(pod_zero_miners)
                else:
                    return len(self.zero_hash_miners)
            
            # Otherwise, load and count directly from CSV
            base_path = '/opt/ngon/data/live/'
            miners_file = os.path.join(base_path, 'miner_status.csv')
            
            if not os.path.exists(miners_file):
                return 0
                
            df_miners = pd.read_csv(miners_file)
            zero_count = 0
            
            for _, row in df_miners.iterrows():
                mac = str(row['mac']).strip().upper()
                hashrate = float(row.get('hs_rt', 0)) if row.get('hs_rt') not in [None, '', 'nan'] else 0
                pod = str(row.get('pod', '')).strip()
                
                # Only count zero hashrate miners that are not sleeping
                if hashrate == 0 and mac not in self.sleeping_miners:
                    if pod_filter and pod_filter != "All Pods":
                        if pod == pod_filter:
                            zero_count += 1
                    else:
                        zero_count += 1
            
            return zero_count
        except Exception:
            return 0

    def get_historical_counts(self, pod_filter=None, days=7):
        """Get historical counts using new simplified buckets."""
        if not self.influx_query_api:
            return []
        
        try:
            # Limit days to prevent issues
            days = min(days, 30)
            
            # Build pod filter for queries
            pod_filter_clause = ""
            if pod_filter and pod_filter != "All Pods":
                pod_filter_clause = f'|> filter(fn: (r) => r._measurement == "{pod_filter}")'
            
            # Build simple queries for each bucket (use 5m windows to match data frequency)
            # Limit to today to avoid date confusion in charts
            queries = {
                'zero_count': f'''
from(bucket: "{self.zero_hash_bucket}")
  |> range(start: -{days}d)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> aggregateWindow(every: 30m, fn: last, createEmpty: false)
  |> group(columns: ["_time"])
  |> sum()
                ''',
                'sleeping_count': f'''
from(bucket: "{self.sleeping_bucket}")
  |> range(start: -{days}d)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> aggregateWindow(every: 30m, fn: last, createEmpty: false)
  |> group(columns: ["_time"])
  |> sum()
                ''',
                'pod_up_offline_count': f'''
from(bucket: "{self.offline_bucket}")
  |> range(start: -{days}d)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> aggregateWindow(every: 30m, fn: last, createEmpty: false)
  |> group(columns: ["_time"])
  |> sum()
                ''',
                'online_hashing_count': f'''
from(bucket: "{self.online_hashing_bucket}")
  |> range(start: -{days}d)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> aggregateWindow(every: 30m, fn: last, createEmpty: false)
  |> group(columns: ["_time"])
  |> sum()
                '''
            }
            
            # Execute queries and combine results
            chart_data = {}
            
            for field_name, query in queries.items():
                try:
                    result = self.influx_query_api.query(org="ngonsolutions", query=query)
                    
                    for table in result:
                        for record in table.records:
                            timestamp = record.get_time()
                            if not timestamp:
                                continue
                                
                            # Convert to local time to avoid timezone issues and use as sorting key
                            local_timestamp = timestamp.replace(tzinfo=timezone.utc).astimezone()
                            time_str = local_timestamp.strftime('%m-%d %H:%M')
                            count = record.get_value() or 0
                            
                            # Use time_str as key to naturally deduplicate and avoid timezone issues
                            if time_str not in chart_data:
                                chart_data[time_str] = {
                                    'zero_count': 0, 
                                    'sleeping_count': 0, 
                                    'pod_up_offline_count': 0,
                                    'pod_down_offline_count': 0,  # Keep for compatibility
                                    'online_hashing_count': 0,
                                    'sort_time': local_timestamp
                                }
                            chart_data[time_str][field_name] = count
                            
                except Exception as e:
                    print(f"Error in InfluxDB query for {field_name}: {str(e)}", file=sys.stderr)
            
            # Convert to the expected format and sort chronologically using sort_time
            historical_data = []
            
            # Sort by the sort_time datetime objects for proper chronological sorting
            def get_sort_time(time_str):
                return chart_data[time_str].get('sort_time', datetime.min)
            
            sorted_time_strs = sorted(chart_data.keys(), key=get_sort_time)
            
            for time_str in sorted_time_strs:
                data_point = chart_data[time_str].copy()  # Make a copy to avoid modifying original
                data_point['timestamp'] = time_str
                # Remove the sort_time field as it's no longer needed
                data_point.pop('sort_time', None)
                
                # For compatibility, keep pod_down_offline_count as 0 since we don't track that granularly anymore
                # The offline bucket contains all offline miners regardless of router status
                data_point['pod_down_offline_count'] = 0
                
                historical_data.append(data_point)
            
            # Limit to prevent memory issues (keep reasonable amount of 5-minute data)
            return historical_data[-2016:]  # ~7 days of 5-minute data 
            
        except Exception as e:
            print(f"InfluxDB historical counts query error: {str(e)}", file=sys.stderr)
            return []



    def get_pod_list(self):
        """Get list of all pods for dropdown."""
        try:
            # Get pod list from config manager instead of from zero hash miners
            pod_counts = config.get_pod_miner_counts()
            return sorted(list(pod_counts.keys()))
        except Exception:
            # Fallback to getting pods from zero hash miners if config manager fails
            pods = set()
            for miner in self.zero_hash_miners:
                if miner.get('pod'):
                    pods.add(miner['pod'])
            return sorted(list(pods))

    def get_csv_offline_counts(self, pod_filter=None):
        """Get current offline counts using simplified calculation: total inventory - online miners."""
        try:
            import pandas as pd
            
            # Load required data
            inventory_file = '/opt/ngon/data/live/miner_inventory.csv'
            miners_file = '/opt/ngon/data/live/miner_status.csv'
            
            # Load online miners
            online_miners = set()
            if os.path.exists(miners_file):
                df_miners = pd.read_csv(miners_file)
                if pod_filter and pod_filter != "All Pods":
                    # Filter to specific pod
                    df_miners = df_miners[df_miners['pod'] == pod_filter]
                online_miners = set(df_miners['mac'].astype(str).str.strip().str.upper())
            
            # Load inventory and count total miners
            total_inventory = 0
            if os.path.exists(inventory_file):
                df_inventory = pd.read_csv(inventory_file)
                
                for _, row in df_inventory.iterrows():
                    site = str(row.get('Site', '')).strip()
                    pod_num = str(row.get('Pod', '')).strip()
                    full_pod = f"{site} {pod_num}"
                    mac = str(row.get('MAC', '')).strip().upper()
                    serial = str(row.get('Serial', '')).strip()
                    
                    # Skip empty positions
                    if (not mac or mac.lower() == 'nan') and (not serial or serial.lower() == 'nan'):
                        continue
                    
                    # Apply pod filter if specified
                    if pod_filter and pod_filter != "All Pods" and full_pod != pod_filter:
                        continue
                    
                    total_inventory += 1
            
            # Calculate offline count: total inventory - online miners
            offline_count = max(0, total_inventory - len(online_miners))
            
            # Return as (total_offline, 0) to maintain compatibility with existing display logic
            return offline_count, 0
            
        except Exception as e:
            print(f"Error getting CSV offline counts: {str(e)}", file=sys.stderr)
            return 0, 0

    def get_current_offline_counts(self, pod_filter=None):
        """Get current offline counts from new simplified buckets.""" 
        if not self.influx_query_api:
            return 0, 0
            
        try:
            # Build pod filter for query
            pod_filter_clause = ""
            if pod_filter and pod_filter != "All Pods":
                pod_filter_clause = f'|> filter(fn: (r) => r._measurement == "{pod_filter}")'
            
            # Query offline count from simplified bucket
            query = f'''
from(bucket: "{self.offline_bucket}")
  |> range(start: -30m)
  {pod_filter_clause}
  |> filter(fn: (r) => r._field == "count")
  |> last()
  |> sum()
            '''
            
            result = self.influx_query_api.query(org="ngonsolutions", query=query)
            
            # Extract offline count
            offline_count = 0
            for table in result:
                for record in table.records:
                    offline_count = int(record.get_value() or 0)
                    break
            
            # Return as pod_up_offline for compatibility (we don't separate pod up/down anymore)
            return offline_count, 0
            
        except Exception as e:
            print(f"Error getting current offline counts from InfluxDB: {str(e)}", file=sys.stderr)
            return 0, 0

    def get_pod_statistics(self, pod_name):
        """Get pod statistics including total installed and offline counts."""
        if pod_name == "All Pods":
            return self.get_all_pods_statistics()
            
        try:
            # Count total installed from inventory CSV (Serial OR MAC)
            total_installed = 0
            inventory_file = '/opt/ngon/data/live/miner_inventory.csv'
            if os.path.exists(inventory_file):
                import pandas as pd
                df_inventory = pd.read_csv(inventory_file)
                
                # Split pod_name into site and pod number (e.g., "Alpha 0" -> "Alpha" and "0")
                if ' ' in pod_name:
                    site_name = pod_name.rsplit(' ', 1)[0]  # Everything before last space
                    pod_number = pod_name.rsplit(' ', 1)[1]  # Everything after last space
                else:
                    # Handle single name pods like "John"
                    site_name = pod_name
                    pod_number = ''
                
                # Filter by site and pod number
                if pod_number:
                    pod_inventory = df_inventory[
                        (df_inventory['Site'] == site_name) & 
                        (df_inventory['Pod'].astype(str) == pod_number)
                    ]
                else:
                    # For single name pods, match just the site
                    pod_inventory = df_inventory[df_inventory['Site'] == site_name]
                
                # Count rows where Serial is not empty OR MAC is not empty
                total_installed = len(pod_inventory[
                    (pod_inventory['Serial'].notna() & (pod_inventory['Serial'] != '')) |
                    (pod_inventory['MAC'].notna() & (pod_inventory['MAC'] != ''))
                ])
            
            # Count miners that are online and hashing (hashrate > 0)
            online_hashing = 0
            total_in_status = 0
            miners_file = '/opt/ngon/data/live/miner_status.csv'
            if os.path.exists(miners_file):
                import pandas as pd
                df_miners = pd.read_csv(miners_file)
                pod_miners = df_miners[df_miners['pod'] == pod_name]
                total_in_status = len(pod_miners)
                online_hashing = len(pod_miners[pod_miners['hs_rt'] > 0])
            
            # Calculate offline count (total installed minus all reporting)
            total_offline = max(0, total_installed - total_in_status)
            
            return {
                'total_installed': total_installed,
                'online_hashing': online_hashing,
                'total_offline': total_offline
            }
            
        except Exception as e:
            return None
    
    def get_all_pods_statistics(self):
        """Get statistics for all pods combined."""
        try:
            # Count total installed from inventory CSV (Serial OR MAC)
            total_installed = 0
            inventory_file = '/opt/ngon/data/live/miner_inventory.csv'
            if os.path.exists(inventory_file):
                import pandas as pd
                df_inventory = pd.read_csv(inventory_file)
                # Count miners using EXACT same logic as hashrate script (requires MAC and pod)
                inventory_miners = {}
                for _, row in df_inventory.iterrows():
                    site = str(row.get('Site', '')).strip()
                    pod_num = str(row.get('Pod', '')).strip()
                    full_pod = f"{site} {pod_num}"
                    
                    mac = str(row.get('MAC', '')).strip().upper()
                    serial = str(row.get('Serial', '')).strip()
                    
                    # Skip empty positions (where both serial and mac are empty or 'nan')
                    if (not mac or mac.lower() == 'nan') and (not serial or serial.lower() == 'nan'):
                        continue
                    
                    # Only count if mac AND full_pod exist (exact hashrate script logic)
                    if mac and full_pod:
                        inventory_miners[mac] = full_pod
                
                total_installed = len(inventory_miners)
            
            # Count miners that are online and hashing (hashrate > 0)
            online_hashing = 0
            total_in_status = 0
            miners_file = '/opt/ngon/data/live/miner_status.csv'
            if os.path.exists(miners_file):
                import pandas as pd
                df_miners = pd.read_csv(miners_file)
                total_in_status = len(df_miners)
                online_hashing = len(df_miners[df_miners['hs_rt'] > 0])
            
            # Calculate offline count (total installed minus all reporting)
            total_offline = max(0, total_installed - total_in_status)
            
            return {
                'total_installed': total_installed,
                'online_hashing': online_hashing,
                'total_offline': total_offline
            }
            
        except Exception as e:
            return None

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_zero_hash_table(miners):
    """Generate HTML table for zero hashrate miners."""
    if not miners:
        return "<tr><td colspan='8' style='text-align: center; color: #666;'>No zero hashrate miners found</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'])
        
        # 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: #ff6600;">{escape(error_display)}</span>'
        
        row_html = f'''
        <tr>
            <td><input type="checkbox" class="miner-checkbox" data-mac="{escape(miner['mac'])}" data-ip="{escape(miner['ip'])}"></td>
            <td>{escape(miner['pod'])}</td>
            <td>{escape(miner['position'])}</td>
            <td>{escape(miner['miner_type'])}</td>
            <td>{error_cell}</td>
            <td>{uptime_formatted}</td>
            <td>{escape(miner['serial'])}</td>
            <td>{escape(miner['mac'])}</td>
            <td>{escape(miner['ip'])}</td>
        </tr>
        '''
        rows_html.append(row_html)
    
    return ''.join(rows_html)

# Main execution
print("Content-Type: text/html\n")

# Parse form data
form = cgi.FieldStorage()
selected_pod = form.getvalue('pod', 'All Pods')
days_range = int(form.getvalue('days', 2))
load_miners = form.getvalue('load_miners', 'false').lower() == 'true'
load_charts = form.getvalue('load_charts', 'true').lower() == 'true'  # Charts on by default

# Initialize analyzer and load data
try:
    analyzer = ZeroHashrateAnalyzer(load_miner_details=load_miners)
except MemoryError:
    print("Content-Type: text/html\n")
    print("<html><body><h1>Memory Error</h1><p>The system ran out of memory processing this request. Please try with fewer days or a specific pod filter.</p></body></html>")
    sys.exit(1)

# Only load individual miners if requested
if load_miners:
    filtered_miners = analyzer.zero_hash_miners
    if selected_pod != "All Pods":
        filtered_miners = [m for m in analyzer.zero_hash_miners if m['pod'] == selected_pod]
else:
    filtered_miners = []

# Get historical data (only if charts are enabled)
if load_charts:
    try:
        historical_data = analyzer.get_historical_counts(selected_pod, days_range)
    except (MemoryError, Exception) as e:
        print(f"Warning: Charts disabled due to memory constraints: {str(e)}", file=sys.stderr)
        historical_data = []
        load_charts = False  # Disable charts if memory issues
else:
    historical_data = []

# Get current counts from InfluxDB for display purposes in stats section
influx_zero_count, influx_sleeping_count, influx_offline_count = analyzer.get_latest_influx_count(selected_pod)

# Fallback to CSV if InfluxDB is unavailable
csv_zero_count = influx_zero_count if influx_zero_count is not None else analyzer.get_csv_zero_count(selected_pod)
csv_sleeping_count = influx_sleeping_count if influx_sleeping_count is not None else analyzer.get_csv_sleeping_count(selected_pod)
csv_pod_up_offline_count = influx_offline_count if influx_offline_count is not None else analyzer.get_csv_offline_counts(selected_pod)[0]
csv_pod_down_offline_count = 0  # We don't distinguish pod up/down anymore

# InfluxDB historical data already includes the most recent data points (updated every 5 minutes)
# No need to manually add current data - just use historical_data as-is

# Get pod list for dropdown
pod_list = ["All Pods"] + analyzer.get_pod_list()

# Get pod statistics if a specific pod is selected
pod_stats = analyzer.get_pod_statistics(selected_pod)

# Generate chart data for JavaScript
chart_labels = [item['timestamp'] for item in historical_data]
chart_zero_data = [item.get('zero_count', 0) for item in historical_data]
chart_sleeping_data = [item.get('sleeping_count', 0) for item in historical_data]
chart_pod_up_offline_data = [item.get('pod_up_offline_count', 0) for item in historical_data]
chart_pod_down_offline_data = [item.get('pod_down_offline_count', 0) for item in historical_data]

# Generate additional pod stats if available
additional_pod_stats = ""
if pod_stats:
    additional_pod_stats = f'''
                <div class="stat-item">
                    <div class="stat-number">{pod_stats["total_installed"]}</div>
                    <div class="stat-label">Total Installed</div>
                </div>
                <div class="stat-item">
                    <div class="stat-number" style="color: #00ff00;">{pod_stats["online_hashing"]}</div>
                    <div class="stat-label">Online/Hashing</div>
                </div>'''

# Generate table section if loading miners
table_section = ""
if load_miners:
    table_section = f'''
            <table>
                <thead>
                    <tr>
                        <th><input type="checkbox" id="selectAll" onchange="toggleSelectAll()"></th>
                        <th>Pod</th>
                        <th>Position</th>
                        <th>Miner Type</th>
                        <th>Error Codes</th>
                        <th>Uptime</th>
                        <th>Serial</th>
                        <th>MAC</th>
                        <th>IP</th>
                    </tr>
                </thead>
                <tbody>
                    {generate_zero_hash_table(filtered_miners)}
                </tbody>
            </table>'''

# Generate chart section if loading charts
chart_section = ""
if load_charts:
    chart_section = f'''
        <div class="chart-container">
            <h2 style="margin-bottom: 20px; color: #00ff00;">Miner Status Trends - {selected_pod} ({days_range} days)</h2>
            <div class="chart-wrapper">
                <canvas id="zeroHashChart"></canvas>
            </div>
        </div>'''
else:
    chart_section = '<div class="chart-container"><p style="color: #ccc; font-style: italic; text-align: center; margin: 40px;">Enable "Load Charts" to view historical trends</p></div>'

# Generate chart JavaScript if loading charts
chart_javascript = ""
if load_charts:
    chart_javascript = f'''
        // Chart configuration
        const chartLabels = {json.dumps(chart_labels)};
        const chartZeroData = {json.dumps(chart_zero_data)};
        const chartSleepingData = {json.dumps(chart_sleeping_data)};
        const chartPodUpOfflineData = {json.dumps(chart_pod_up_offline_data)};
        const chartPodDownOfflineData = {json.dumps(chart_pod_down_offline_data)};
        
        const ctx = document.getElementById('zeroHashChart').getContext('2d');
        const zeroHashChart = new Chart(ctx, {{
            type: 'line',
            data: {{
                labels: chartLabels,
                datasets: [{{
                    label: 'Zero Hashrate Miners',
                    data: chartZeroData,
                    borderColor: '#ff6600',
                    backgroundColor: 'rgba(255, 102, 0, 0.05)',
                    borderWidth: 1,
                    fill: true,
                    tension: 0.3,
                    pointRadius: 2,
                    pointHoverRadius: 4
                }}, {{
                    label: 'Sleeping Miners',
                    data: chartSleepingData,
                    borderColor: '#4a9eff',
                    backgroundColor: 'rgba(74, 158, 255, 0.05)',
                    borderWidth: 1,
                    fill: true,
                    tension: 0.3,
                    pointRadius: 2,
                    pointHoverRadius: 4
                }}, {{
                    label: 'Offline Miners',
                    data: chartPodUpOfflineData,
                    borderColor: '#ff4757',
                    backgroundColor: 'rgba(255, 71, 87, 0.05)',
                    borderWidth: 1,
                    fill: true,
                    tension: 0.3,
                    pointRadius: 2,
                    pointHoverRadius: 4
                }}]
            }},
            options: {{
                responsive: true,
                maintainAspectRatio: false,
                plugins: {{
                    legend: {{
                        labels: {{
                            color: '#ffffff'
                        }}
                    }}
                }},
                scales: {{
                    x: {{
                        ticks: {{
                            color: '#ffffff',
                            maxTicksLimit: 12
                        }},
                        grid: {{
                            color: 'rgba(255,255,255,0.1)'
                        }}
                    }},
                    y: {{
                        ticks: {{
                            color: '#ffffff'
                        }},
                        grid: {{
                            color: 'rgba(255,255,255,0.1)'
                        }}
                    }}
                }}
            }}
        }});'''

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

# Generate header with controls
additional_controls = f'''
    <div class="header-controls">
        <div class="control-group">
            <label class="control-label">Pod Selection</label>
            <select id="podSelect" onchange="updateView()">
                {chr(10).join(f'<option value="{pod}" {"selected" if pod == selected_pod else ""}>{pod}</option>' for pod in pod_list)}
            </select>
        </div>
        <div class="control-group">
            <label class="control-label">Time Range</label>
            <select id="daysSelect" onchange="updateView()">
                <option value="2" {"selected" if days_range == 2 else ""}>48 Hours</option>
                <option value="7" {"selected" if days_range == 7 else ""}>7 Days</option>
                <option value="30" {"selected" if days_range == 30 else ""}>30 Days</option>
            </select>
        </div>
        <div class="control-group">
            <label class="control-label">Actions</label>
            <div style="display: flex; gap: 10px;">
                <button onclick="refreshData()">Refresh</button>
                <button onclick="exportData()">Export</button>
                <button onclick="pullList()" {"disabled" if not load_miners else ""}>Pull List</button>
            </div>
        </div>
        <div class="control-group">
            <label class="control-label">View Options</label>
            <div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
                <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem;">
                    <input type="checkbox" id="loadChartsToggle" {"checked" if load_charts else ""} onchange="updateView()">
                    Load Charts
                </label>
                <label style="display: flex; align-items: center; gap: 5px; color: #ccc; font-size: 0.9rem;">
                    <input type="checkbox" id="loadMinersToggle" {"checked" if load_miners else ""} onchange="updateView()">
                    Load Miners
                </label>
            </div>
        </div>
    </div>
'''

# Generate header HTML manually to include description
header_html = f'''
<div class="header">
    <div>
        <div class="dropdown">
            <h1 class="dropdown-title">NGON Mining - Zero Hash Analysis</h1>
            <div class="dropdown-content">{generate_dropdown_html(AuthManager.get_user_access())}</div>
        </div>
        <p style="color: #888; font-size: 14px; margin: 6px 0 0 0;">Tracking idle miners and recovery patterns</p>
    </div>
    {additional_controls}
</div>
'''

# Generate HTML
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>Zero Hashrate Analysis</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        {generate_dropdown_css()}
    </style>
    <style>
        * {{ margin: 0; padding: 0; box-sizing: border-box; }}
        body {{
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #1a1a1a;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 20px;
            line-height: 1.4;
        }}
        .container {{ max-width: none; margin: 0 auto; }}

        .header {{
            background-color: rgba(255,255,255,0.05) !important;
            border-radius: 8px !important;
            padding: 30px !important;
            margin-bottom: 30px;
            border: 1px solid rgba(255,255,255,0.1) !important;
            border-bottom: none !important;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
            flex-wrap: wrap;
            gap: 20px;
        }}

        .header h1.dropdown-title {{
            font-size: 2.2em !important;
            font-weight: 600 !important;
            margin: 3px 0 0 0 !important;
            line-height: 1.2 !important;
            color: #00ff00 !important;
        }}
        
        .header-controls {{
            display: flex;
            gap: 15px;
            align-items: center;
            flex-wrap: wrap;
        }}
        
        .control-group {{
            display: flex;
            flex-direction: column;
            gap: 5px;
        }}
        
        .control-label {{
            color: #ccc;
            font-size: 0.9rem;
            font-weight: 500;
        }}
        
        select, button {{
            padding: 10px 15px;
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 4px;
            background: #2a2a2a;
            color: #ffffff;
            font-size: 0.9rem;
            cursor: pointer;
            transition: all 0.3s ease;
        }}
        
        select:hover, button:hover {{
            background: #3a3a3a;
            border-color: rgba(255,255,255,0.3);
        }}
        
        button {{
            background: rgba(0, 122, 204, 0.2);
            border-color: rgba(0, 122, 204, 0.4);
        }}
        
        button:hover {{
            background: rgba(0, 122, 204, 0.3);
        }}
        
        .chart-container {{
            background-color: rgba(255,255,255,0.05);
            border-radius: 8px;
            padding: 30px;
            margin-bottom: 30px;
            border: 1px solid rgba(255,255,255,0.1);
        }}
        
        .chart-wrapper {{
            position: relative;
            height: 400px;
            width: 100%;
        }}
        
        .miners-table {{
            background-color: rgba(255,255,255,0.05);
            border-radius: 8px;
            padding: 30px;
            border: 1px solid rgba(255,255,255,0.1);
        }}
        
        table {{
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }}
        
        th, td {{
            padding: 12px;
            text-align: left;
            border-bottom: 1px solid rgba(255,255,255,0.1);
        }}
        
        th {{
            background-color: rgba(255,255,255,0.1);
            font-weight: 600;
            color: #ffffff;
        }}
        
        tr:hover {{
            background-color: rgba(255,255,255,0.05);
        }}
        
        .stats {{
            display: flex;
            gap: 20px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }}
        
        .stat-item {{
            background-color: rgba(255,255,255,0.1);
            padding: 15px 20px;
            border-radius: 6px;
            text-align: center;
        }}
        
        .stat-number {{
            font-size: 1.8em;
            font-weight: bold;
            color: #ff6600;
        }}
        
        .stat-label {{
            font-size: 0.9em;
            color: #ccc;
            margin-top: 5px;
        }}
        
        .error-modal {{
            display: none;
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.8);
        }}
        
        .modal-content {{
            background-color: #2a2a2a;
            margin: 15% auto;
            padding: 20px;
            border: 1px solid #555;
            border-radius: 8px;
            width: 80%;
            max-width: 600px;
            color: #ffffff;
        }}
        
        .close {{
            color: #aaa;
            float: right;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
        }}
        
        .close:hover {{
            color: #fff;
        }}
    </style>
</head>
<body>
    <div class="container">
        {header_html}
        
        {chart_section}
        
        <div class="miners-table">
            <div class="stats">
                <div class="stat-item">
                    <div class="stat-number" style="color: #ff6600;">{csv_zero_count}</div>
                    <div class="stat-label">Zero Hashrate Miners</div>
                </div>
                <div class="stat-item">
                    <div class="stat-number" style="color: #4a9eff;">{csv_sleeping_count}</div>
                    <div class="stat-label">Sleeping Miners</div>
                </div>
                <div class="stat-item">
                    <div class="stat-number" style="color: #ff4757;">{csv_pod_up_offline_count}</div>
                    <div class="stat-label">Offline Miners</div>
                </div>
                {additional_pod_stats}
            </div>
            
            {'<h2 style="color: #00ff00;">Current Zero Hashrate Miners</h2>' if load_miners else '<p style="color: #ccc; font-style: italic; text-align: center; margin: 40px;">Enable "Load Miners" to view individual miner details</p>'}
            {table_section}
        </div>
    </div>
    
    <!-- Error Modal -->
    <div id="errorModal" class="error-modal">
        <div class="modal-content">
            <span class="close" onclick="closeErrorModal()">&times;</span>
            <h3 id="errorModalTitle">Error Code Details</h3>
            <div id="errorModalContent"></div>
        </div>
    </div>
    
    <script>
        {chart_javascript}
        
        function updateView() {{
            const pod = document.getElementById('podSelect').value;
            const days = document.getElementById('daysSelect').value;
            const loadCharts = document.getElementById('loadChartsToggle').checked ? 'true' : 'false';
            const loadMiners = document.getElementById('loadMinersToggle').checked ? 'true' : 'false';
            window.location.href = `?pod=${{encodeURIComponent(pod)}}&days=${{days}}&load_charts=${{loadCharts}}&load_miners=${{loadMiners}}`;
        }}
        
        function refreshData() {{
            window.location.reload();
        }}
        
        function exportData() {{
            const table = document.querySelector('table');
            const headerRow = table.querySelector('thead tr');
            const bodyRows = table.querySelectorAll('tbody tr');
            
            let csvContent = '';
            
            // Skip checkbox column (index 0) - define which columns to export
            const columnsToExport = [1, 2, 3, 4, 5, 6, 7, 8]; // All columns except checkbox
            
            // Add header row (only selected columns)
            const headers = columnsToExport.map(index => 
                headerRow.cells[index].textContent.replace(/[↑↓↕]/g, '').trim()
            );
            csvContent += headers.map(header => `"${{header}}"`).join(',') + '\\n';
            
            // Add only visible data rows (only selected columns)
            bodyRows.forEach(row => {{
                // Skip hidden rows (filtered out)
                if (row.style.display === 'none') return;
                
                const rowData = columnsToExport.map(index => {{
                    let content = row.cells[index].textContent.trim();
                    // Escape quotes and wrap in quotes
                    content = content.replace(/"/g, '""');
                    return `"${{content}}"`;
                }});
                csvContent += rowData.join(',') + '\\n';
            }});
            
            // Create and download file
            const blob = new Blob([csvContent], {{ type: 'text/csv;charset=utf-8;' }});
            const link = document.createElement('a');
            const url = URL.createObjectURL(blob);
            link.setAttribute('href', url);
            
            // Generate filename based on selected pod
            const podSelect = document.getElementById('podSelect');
            let filename = 'zero_hash_miners';
            if (podSelect.value !== 'All Pods') {{
                filename = podSelect.value.replace(/[^a-zA-Z0-9]/g, '_') + '_zero_hash';
            }}
            link.setAttribute('download', `${{filename}}_${{new Date().toISOString().slice(0,10)}}.csv`);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }}
        
        function pullList() {{
            const selectedCheckboxes = document.querySelectorAll('.miner-checkbox:checked');
            
            if (selectedCheckboxes.length === 0) {{
                alert('Please select miners for pull list');
                return;
            }}
            
            // Get selected miners
            const pullListData = [];
            selectedCheckboxes.forEach(checkbox => {{
                const row = checkbox.closest('tr');
                const pod = row.cells[1].textContent.trim();
                const position = row.cells[2].textContent.trim();
                const serial = row.cells[6].textContent.trim();
                const lastFiveSerial = serial.slice(-5);
                
                pullListData.push({{
                    pod: pod,
                    position: position,
                    serial: lastFiveSerial
                }});
            }});
            
            // Format the pull list text
            let pullListText = '';
            pullListData.forEach(item => {{
                pullListText += `${{item.pod}} - ${{item.position}} - ${{item.serial}}\\n`;
            }});
            
            // Create the popup
            const backdrop = document.createElement('div');
            backdrop.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.5);
                z-index: 1000;
                display: flex;
                align-items: center;
                justify-content: center;
            `;
            
            const dialog = document.createElement('div');
            dialog.style.cssText = `
                background: #2a2a2a;
                color: #ffffff;
                padding: 30px;
                border-radius: 8px;
                border: 1px solid rgba(255,255,255,0.2);
                max-width: 500px;
                width: 90%;
                max-height: 70vh;
                overflow-y: auto;
            `;
            
            dialog.innerHTML = `
                <h3 style="margin-bottom: 20px;">Pull List (${{pullListData.length}} miners)</h3>
                <textarea readonly style="
                    width: 100%;
                    height: 300px;
                    background: #1a1a1a;
                    color: #ffffff;
                    border: 1px solid rgba(255,255,255,0.2);
                    border-radius: 4px;
                    padding: 15px;
                    font-family: 'Courier New', monospace;
                    font-size: 14px;
                    resize: none;
                    margin-bottom: 20px;
                ">${{pullListText}}</textarea>
                <div style="display: flex; gap: 10px; justify-content: flex-end;">
                    <button onclick="navigator.clipboard.writeText(this.parentElement.previousElementSibling.value).then(() => {{ this.textContent = 'Copied!'; setTimeout(() => {{ this.textContent = 'Copy'; }}, 1500); }})" style="
                        background: rgba(0, 122, 204, 0.2);
                        border: 1px solid rgba(0, 122, 204, 0.4);
                        color: #ffffff;
                        padding: 10px 20px;
                        border-radius: 4px;
                        cursor: pointer;
                    ">Copy</button>
                    <button onclick="this.closest('.backdrop').remove()" style="
                        background: rgba(255,255,255,0.1);
                        border: 1px solid rgba(255,255,255,0.2);
                        color: #ffffff;
                        padding: 10px 20px;
                        border-radius: 4px;
                        cursor: pointer;
                    ">Close</button>
                </div>
            `;
            
            backdrop.className = 'backdrop';
            backdrop.appendChild(dialog);
            document.body.appendChild(backdrop);
            
            // Close on backdrop click
            backdrop.addEventListener('click', (e) => {{
                if (e.target === backdrop) {{
                    backdrop.remove();
                }}
            }});
        }}
        
        function toggleSelectAll() {{
            const selectAll = document.getElementById('selectAll');
            const checkboxes = document.querySelectorAll('.miner-checkbox');
            checkboxes.forEach(cb => cb.checked = selectAll.checked);
        }}
        
        function showErrorCodes(mac, errorCodes) {{
            const modal = document.getElementById('errorModal');
            const title = document.getElementById('errorModalTitle');
            const content = document.getElementById('errorModalContent');
            
            title.textContent = `Error Codes for ${{mac}}`;
            content.innerHTML = `<p><strong>Error Codes:</strong> ${{errorCodes}}</p>`;
            
            modal.style.display = 'block';
        }}
        
        function closeErrorModal() {{
            document.getElementById('errorModal').style.display = 'none';
        }}
        
        // Close modal when clicking outside
        window.onclick = function(event) {{
            const modal = document.getElementById('errorModal');
            if (event.target === modal) {{
                modal.style.display = 'none';
            }}
        }}

        // Multi-select functionality with shift-click
        let lastClickedCheckbox = null;
        
        document.addEventListener('DOMContentLoaded', function() {{
            // Add event listeners to all checkboxes for multi-select
            document.querySelectorAll('.miner-checkbox').forEach(checkbox => {{
                checkbox.addEventListener('click', function(e) {{
                    if (e.shiftKey && lastClickedCheckbox && lastClickedCheckbox !== this) {{
                        // Get all checkboxes
                        const allCheckboxes = Array.from(document.querySelectorAll('.miner-checkbox'));
                        const currentIndex = allCheckboxes.indexOf(this);
                        const lastIndex = allCheckboxes.indexOf(lastClickedCheckbox);
                        
                        // Determine range
                        const startIndex = Math.min(currentIndex, lastIndex);
                        const endIndex = Math.max(currentIndex, lastIndex);
                        
                        // Set all checkboxes in range to the same state as the current one
                        const targetState = this.checked;
                        for (let i = startIndex; i <= endIndex; i++) {{
                            allCheckboxes[i].checked = targetState;
                        }}
                    }}
                    
                    lastClickedCheckbox = this;
                }});
            }});
        }});
    </script>
    
    <script>
        {generate_dropdown_js()}
    </script>
</body>
</html>
'''

print(html_content)