#!/usr/bin/env python3
import cgi, sys
sys.path.insert(0, '/opt/ngon/apps')
sys.path.insert(0, '/var/www/html/ngon')
from managers.auth_manager import AuthManager, generate_login_page_html
from links import generate_dropdown_html, generate_dropdown_css, generate_dropdown_js

_form = cgi.FieldStorage()
_auth = AuthManager('miner_report')
_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("Miner Pod Report"))
    sys.exit(0)

_user_access = AuthManager.get_user_access()


"""
Miner Report Viewer
Web interface for viewing hourly miner pod snapshots from SQLite history.
"""

import cgi
import json
import sys
import sqlite3
from datetime import datetime
import cgitb

cgitb.enable()

sys.path.insert(0, '/opt/ngon')
from apps.miners.error_categories import categorize_error_codes

# Configuration
DB_PATH = "/opt/ngon/data/miner_history.db"
MASTER_CONFIG_PATH = "/opt/ngon/config/master_config.json"


def load_pod_config():
    """Load pod config (miner_count, miner_type) from master_config.json."""
    try:
        with open(MASTER_CONFIG_PATH, 'r') as f:
            config = json.load(f)

        miner_specs = {}
        for miner_type, specs in config.get('misc', {}).get('miner_types', {}).items():
            miner_specs[miner_type] = float(specs.get('spec_hashrate', 180.0))

        pod_config = {}
        for site_name, site_data in config.get('sites', {}).items():
            for group_name, group_data in site_data.get('generator_groups', {}).items():
                for pod_name, pod_data in group_data.get('pods', {}).items():
                    miner_type = pod_data.get('miner_type', 'Unknown')
                    spec_hs = miner_specs.get(miner_type, 180.0)
                    pod_config[pod_name] = {
                        'miner_count': pod_data.get('miner_count', 0),
                        'miner_type': miner_type,
                        'spec_hashrate': spec_hs,
                        'low_hashrate_threshold': spec_hs * 0.8
                    }
        return pod_config
    except Exception as e:
        return {}


def get_data_range():
    """Get the min and max snapshot timestamps available in the DB."""
    try:
        conn = sqlite3.connect(f'file:{DB_PATH}?immutable=1', uri=True)
        cur = conn.cursor()
        cur.execute("SELECT MIN(timestamp), MAX(timestamp) FROM miner_snapshots")
        row = cur.fetchone()
        conn.close()
        if row and row[0] and row[1]:
            return row[0], row[1]
        return None, None
    except Exception:
        return None, None


def load_snapshot_data(target_dt, pod_config):
    """Load the nearest snapshot to target_dt, aggregated per pod with error categorization.

    target_dt: a datetime string like '2026-03-06T06:00' or a datetime object.
    Finds the closest snapshot timestamp to the target.
    """
    if isinstance(target_dt, str):
        target_str = target_dt.replace('T', 'T')  # already ISO-ish
    else:
        target_str = target_dt.isoformat()

    try:
        conn = sqlite3.connect(f'file:{DB_PATH}?immutable=1', uri=True)
        conn.row_factory = sqlite3.Row
        cur = conn.cursor()

        # Find the closest snapshot timestamp to the target
        # Check both directions and pick the nearest
        cur.execute("""
            SELECT timestamp FROM (
                SELECT DISTINCT timestamp FROM miner_snapshots
                WHERE timestamp <= ? ORDER BY timestamp DESC LIMIT 1
            )
            UNION ALL
            SELECT timestamp FROM (
                SELECT DISTINCT timestamp FROM miner_snapshots
                WHERE timestamp > ? ORDER BY timestamp ASC LIMIT 1
            )
        """, (target_str, target_str))
        candidates = cur.fetchall()

        if not candidates:
            conn.close()
            return None, "No snapshot data available"

        # Pick the closest
        snapshot_ts = min(
            [r['timestamp'] for r in candidates],
            key=lambda t: abs(
                datetime.fromisoformat(t).timestamp() -
                datetime.fromisoformat(target_str).timestamp()
            )
        )

        # Get all miners from that snapshot
        cur.execute("""
            SELECT pod, miner_type, hs_rt, error_codes, mining_state, mac
            FROM miner_snapshots
            WHERE timestamp = ?
        """, (snapshot_ts,))
        miners = cur.fetchall()
        conn.close()

        # Aggregate per pod
        pods_data = {}
        # Initialize all configured pods
        for pod_name, pc in pod_config.items():
            pods_data[pod_name] = {
                'pod_status': 'offline',
                'miner_type': pc['miner_type'],
                'miners_installed': pc['miner_count'],
                'miners_online': 0,
                'miners_hashing': 0,
                'miners_low_hashrate': 0,
                'miners_zero_hashrate': 0,
                'miners_offline': 0,
                'miners_sleeping': 0,
                'miners_with_errors': 0,
                'total_error_instances': 0,
                'error_summary': {
                    'miners_with_fan_errors': 0,
                    'miners_with_power_errors': 0,
                    'miners_with_temp_sensor_errors': 0,
                    'miners_with_overtemp_protection': 0,
                    'miners_with_hashboard_errors': 0,
                    'miners_with_pool_errors': 0,
                    'miners_with_system_errors': 0,
                }
            }

        # Process each miner row
        for miner in miners:
            pod = miner['pod']
            if pod not in pods_data:
                # Pod in DB but not in config - create entry
                pods_data[pod] = {
                    'pod_status': 'offline',
                    'miner_type': miner['miner_type'] or 'Unknown',
                    'miners_installed': 0,
                    'miners_online': 0,
                    'miners_hashing': 0,
                    'miners_low_hashrate': 0,
                    'miners_zero_hashrate': 0,
                    'miners_offline': 0,
                    'miners_sleeping': 0,
                    'miners_with_errors': 0,
                    'total_error_instances': 0,
                    'error_summary': {
                        'miners_with_fan_errors': 0,
                        'miners_with_power_errors': 0,
                        'miners_with_temp_sensor_errors': 0,
                        'miners_with_overtemp_protection': 0,
                        'miners_with_hashboard_errors': 0,
                        'miners_with_pool_errors': 0,
                        'miners_with_system_errors': 0,
                    }
                }

            pd = pods_data[pod]
            hs_rt = miner['hs_rt'] or 0.0
            mining_state = (miner['mining_state'] or '').lower()

            if mining_state == 'sleeping':
                pd['miners_sleeping'] += 1
            else:
                pd['miners_online'] += 1
                if hs_rt > 0:
                    pd['miners_hashing'] += 1
                    # Check low hashrate
                    pc = pod_config.get(pod, {})
                    threshold = pc.get('low_hashrate_threshold', 144.0)
                    if hs_rt < threshold:
                        pd['miners_low_hashrate'] += 1
                else:
                    pd['miners_zero_hashrate'] += 1

                # Mark pod as online if any non-sleeping miner present
                pd['pod_status'] = 'online'

            # Error categorization
            error_codes_str = miner['error_codes'] or ''
            cat = categorize_error_codes(error_codes_str)
            if cat['has_errors']:
                pd['miners_with_errors'] += 1
                pd['total_error_instances'] += cat['total_error_count']
                for key in pd['error_summary']:
                    pd['error_summary'][key] += cat[key]

        # Calculate offline counts
        for pod_name, pd in pods_data.items():
            pd['miners_offline'] = max(0, pd['miners_installed'] - pd['miners_online'] - pd['miners_sleeping'])

        return {'pods': pods_data, 'generated_at': snapshot_ts}, None

    except Exception as e:
        return None, f"Database error: {str(e)}"


def format_percentage(value, total):
    """Format a percentage value."""
    if total == 0:
        return "0%"
    return f"{(value/total)*100:.1f}%"


def get_status_color(pod_status, miners_hashing, miners_installed):
    """Get color class for pod status."""
    if pod_status == "offline":
        return "offline"
    elif miners_installed == 0:
        return "offline"
    elif miners_hashing == 0:
        return "critical"
    elif miners_hashing / miners_installed < 0.5:
        return "warning"
    else:
        return "healthy"


def render_pod_table(pods_data):
    """Render the main pod table."""
    html = """
    <table class="pod-table">
        <thead>
            <tr>
                <th>Pod Name</th>
                <th>Type</th>
                <th>Status</th>
                <th>Installed</th>
                <th>Online</th>
                <th>Hashing</th>
                <th>Low Hash</th>
                <th>Zero Hash</th>
                <th>Offline</th>
                <th>Sleeping</th>
                <th>Miners w/ Errors</th>
                <th>Total Errors</th>
                <th colspan="7">Error Summary</th>
            </tr>
            <tr class="sub-header">
                <th colspan="12"></th>
                <th>Fan</th>
                <th>Power</th>
                <th>Temp Sensor</th>
                <th>Over Temp</th>
                <th>Board</th>
                <th>Pool</th>
                <th>System</th>
            </tr>
        </thead>
        <tbody>
    """

    # Sort pods by name
    sorted_pods = sorted(pods_data.items(), key=lambda x: x[0])

    for pod_name, pod_data in sorted_pods:
        status_class = get_status_color(
            pod_data['pod_status'],
            pod_data['miners_hashing'],
            pod_data['miners_installed']
        )

        errors = pod_data['error_summary']
        total_errors = sum(errors.values())

        html += f"""
            <tr class="pod-row {status_class}">
                <td class="pod-name">{pod_name}</td>
                <td>{pod_data['miner_type']}</td>
                <td class="status-{pod_data['pod_status']}">{pod_data['pod_status']}</td>
                <td>{pod_data['miners_installed']}</td>
                <td>{pod_data['miners_online']}</td>
                <td>{pod_data['miners_hashing']}</td>
                <td>{pod_data['miners_low_hashrate']}</td>
                <td>{pod_data['miners_zero_hashrate']}</td>
                <td>{pod_data['miners_offline']}</td>
                <td>{pod_data.get('miners_sleeping', 0)}</td>
                <td class="error-count">{pod_data.get('miners_with_errors', 0)}</td>
                <td class="error-count">{pod_data.get('total_error_instances', 0)}</td>
                <td class="error-count">{errors['miners_with_fan_errors']}</td>
                <td class="error-count">{errors['miners_with_power_errors']}</td>
                <td class="error-count">{errors.get('miners_with_temp_sensor_errors', 0)}</td>
                <td class="error-count">{errors.get('miners_with_overtemp_protection', 0)}</td>
                <td class="error-count">{errors['miners_with_hashboard_errors']}</td>
                <td class="error-count">{errors['miners_with_pool_errors']}</td>
                <td class="error-count">{errors['miners_with_system_errors']}</td>
            </tr>
        """

    html += """
        </tbody>
    </table>
    """

    return html


def main():
    print("Content-Type: text/html\n")

    form = cgi.FieldStorage()
    selected_dt = form.getvalue('dt', '')

    pod_config = load_pod_config()
    data_min, data_max = get_data_range()

    # Load report data
    if selected_dt:
        report_data, error = load_snapshot_data(selected_dt, pod_config)
    elif data_max:
        report_data, error = load_snapshot_data(data_max, pod_config)
    else:
        report_data, error = None, "No snapshot data available in database"

    print("""
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Miner Pod Reports - NGON Mining</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <style>
""")
    print(generate_dropdown_css())
    print("""
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #1a1a1a;
            color: #e0e0e0;
            line-height: 1.4;
        }

        /* Floating-box header (matches status/gen_manager) */
        .header {
            background-color: rgba(255,255,255,0.05) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            border-radius: 8px !important;
            padding: 30px !important;
            margin: 20px !important;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
            border-bottom: none !important;
        }
        .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 .subtitle {
            margin: 6px 0 0 0;
            color: #888;
            font-size: 14px;
        }

        .controls {
            background-color: rgba(255,255,255,0.05);
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 8px;
            padding: 15px;
            margin: 0 20px 20px 20px;
            color: #e0e0e0;
        }
        .controls label { color: #888; font-size: 12px; }
        .controls select, .controls input[type="datetime-local"] {
            padding: 8px 12px;
            border: 1px solid #3a3a3a;
            border-radius: 4px;
            background: #2a2a2a;
            color: #e0e0e0;
            font-size: 13px;
            color-scheme: dark;
            margin-right: 10px;
        }
        .controls button, .controls a.latest-link {
            padding: 8px 16px;
            background: #00ff00;
            color: #000;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: 600;
            font-size: 13px;
            text-decoration: none;
            display: inline-block;
        }
        .controls button:hover, .controls a.latest-link:hover { background: #00cc00; }

        .summary-cards {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin: 0 20px 20px 20px;
        }
        .summary-card {
            background-color: rgba(255,255,255,0.08);
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 6px;
            padding: 15px;
            text-align: center;
        }
        .summary-card h3 {
            margin: 0 0 10px 0;
            color: #888;
            font-size: 12px;
            text-transform: uppercase;
            letter-spacing: 1px;
            font-weight: 600;
        }
        .summary-card .value {
            font-size: 24px;
            font-weight: bold;
            color: #00ff00;
        }

        .pod-table-wrap {
            background-color: rgba(255,255,255,0.05);
            border: 1px solid rgba(255,255,255,0.1);
            border-radius: 8px;
            padding: 0;
            margin: 0 20px 20px 20px;
            overflow: hidden;
        }
        .pod-table {
            width: 100%;
            border-collapse: collapse;
            color: #e0e0e0;
        }
        .pod-table th {
            background: rgba(0, 255, 0, 0.10);
            color: #00ff00;
            padding: 12px 8px;
            text-align: left;
            font-weight: 600;
            font-size: 12px;
            text-transform: uppercase;
            border-bottom: 1px solid rgba(255,255,255,0.1);
        }
        .pod-table .sub-header th {
            background: rgba(0, 255, 0, 0.05);
            color: #aaa;
            font-size: 11px;
            padding: 8px;
        }
        .pod-table td {
            padding: 10px 8px;
            border-bottom: 1px solid rgba(255,255,255,0.05);
            color: #e0e0e0;
        }
        .pod-table tr:hover {
            background-color: rgba(255,255,255,0.05);
        }
        .pod-name { font-weight: 600; color: #e0e0e0; }
        .status-online  { color: #00ff00; font-weight: 600; }
        .status-offline { color: #ff4444; font-weight: 600; }

        .error-count { text-align: center; font-weight: 600; }
        .error-count:not(:empty) { color: #ff8888; }

        .pod-row.critical { background-color: rgba(220, 53, 69, 0.18); }
        .pod-row.warning  { background-color: rgba(255, 193, 7, 0.15); }
        .pod-row.healthy  { background-color: rgba(40, 167, 69, 0.15); }
        .pod-row.offline  { background-color: rgba(255,255,255,0.03); opacity: 0.7; }

        .error-message {
            background: rgba(220, 53, 69, 0.15);
            border: 1px solid rgba(220, 53, 69, 0.4);
            color: #ff8888;
            padding: 15px;
            border-radius: 8px;
            margin: 0 20px 20px 20px;
        }

        .timestamp {
            text-align: right;
            color: #666;
            font-size: 12px;
            margin: 20px;
        }

        @media (max-width: 768px) {
            .pod-table { font-size: 12px; }
            .pod-table th,
            .pod-table td { padding: 6px 4px; }
        }
    </style>
</head>
<body>
""")
    print(f"""    <div class="header">
        <div>
            <div class="dropdown">
                <h1 class="dropdown-title">NGON Mining - Miner Pod Report</h1>
                <div class="dropdown-content">{generate_dropdown_html(_user_access)}</div>
            </div>
            <div class="subtitle">Hourly mining operation status and error analysis</div>
        </div>
    </div>
    """)

    if error:
        print(f'<div class="error-message">Error: {error}</div>')
    elif report_data:
        # Render date/time picker
        snapshot_ts = report_data.get('generated_at', '')
        # Format current snapshot time for the input value
        if snapshot_ts:
            try:
                picker_value = datetime.fromisoformat(snapshot_ts).strftime('%Y-%m-%dT%H:%M')
            except Exception:
                picker_value = selected_dt or ''
        else:
            picker_value = selected_dt or ''

        min_attr = f'min="{datetime.fromisoformat(data_min).strftime("%Y-%m-%dT%H:%M")}"' if data_min else ''
        max_attr = f'max="{datetime.fromisoformat(data_max).strftime("%Y-%m-%dT%H:%M")}"' if data_max else ''

        print(f'''<div class="controls">
            <form method="get" style="display: flex; align-items: center; gap: 10px;">
                <label for="dt">Snapshot time:</label>
                <input type="datetime-local" id="dt" name="dt" value="{picker_value}" {min_attr} {max_attr}
                       onchange="this.form.submit()">
                <button type="submit">Go</button>
                <a href="?" class="latest-link">Latest</a>
            </form>
        </div>''')

        # Calculate summary statistics
        pods_data = report_data.get('pods', {})
        total_pods = len(pods_data)
        online_pods = sum(1 for pod in pods_data.values() if pod['pod_status'] == 'online')
        total_installed = sum(pod['miners_installed'] for pod in pods_data.values())
        total_hashing = sum(pod['miners_hashing'] for pod in pods_data.values())
        total_errors = sum(sum(pod['error_summary'].values()) for pod in pods_data.values())

        # Render summary cards
        print('<div class="summary-cards">')
        print(f'''
            <div class="summary-card">
                <h3>Total Pods</h3>
                <div class="value">{total_pods}</div>
            </div>
            <div class="summary-card">
                <h3>Pods Online</h3>
                <div class="value">{online_pods}</div>
            </div>
            <div class="summary-card">
                <h3>Miners Installed</h3>
                <div class="value">{total_installed:,}</div>
            </div>
            <div class="summary-card">
                <h3>Miners Hashing</h3>
                <div class="value">{total_hashing:,}</div>
            </div>
            <div class="summary-card">
                <h3>Miners with Errors</h3>
                <div class="value">{total_errors:,}</div>
            </div>
            <div class="summary-card">
                <h3>Hash Rate %</h3>
                <div class="value">{format_percentage(total_hashing, total_installed)}</div>
            </div>
        ''')
        print('</div>')

        # Render main table (wrapped so it gets the panel/border/radius)
        print('<div class="pod-table-wrap">')
        print(render_pod_table(pods_data))
        print('</div>')

        # Render timestamp
        generated_at = report_data.get('generated_at', '')
        if generated_at:
            try:
                dt = datetime.fromisoformat(generated_at.replace('Z', '+00:00'))
                formatted_time = dt.strftime('%Y-%m-%d %H:%M:%S UTC')
                print(f'<div class="timestamp">Snapshot taken: {formatted_time}</div>')
            except Exception:
                print(f'<div class="timestamp">Snapshot taken: {generated_at}</div>')
    else:
        print('<div class="error-message">No report data available</div>')

    print(f"""
    <script>{generate_dropdown_js()}</script>
</body>
</html>
    """)

if __name__ == "__main__":
    main()
