#!/usr/bin/env python3
"""Consent-based, resource-limited MOHO testnet PoW miner (one CPU process).

Safe defaults: no public sockets, no remote code, no GPU, bounded work per burst.
"""
import argparse
import ctypes
import json
import os
import pathlib
import platform
import random
import sys
import time
import urllib.error
import urllib.request

from moho.cli import request
from moho.consensus import find_pow
from moho.crypto import load_wallet


def battery_ok():
    if sys.platform == 'win32':
        class Power(ctypes.Structure):
            _fields_ = [('ACLineStatus', ctypes.c_byte), ('BatteryFlag', ctypes.c_byte),
                        ('BatteryLifePercent', ctypes.c_byte), ('SystemStatusFlag', ctypes.c_byte),
                        ('BatteryLifeTime', ctypes.c_uint32), ('BatteryFullLifeTime', ctypes.c_uint32)]
        data = Power()
        if not ctypes.windll.kernel32.GetSystemPowerStatus(ctypes.byref(data)):
            return False
        return data.ACLineStatus == 1 and (data.BatteryLifePercent < 0 or data.BatteryLifePercent >= 40)
    if sys.platform.startswith('linux'):
        for path in pathlib.Path('/sys/class/power_supply').glob('BAT*'):
            status = path / 'status'
            cap = path / 'capacity'
            try:
                if status.exists() and status.read_text().strip() in ('Discharging', 'Not charging'):
                    return False
                if cap.exists() and int(cap.read_text()) < 40:
                    return False
            except (OSError, ValueError):
                return False
    return True


def should_mine(cpu_limit):
    if not battery_ok():
        return False, 'battery'
    try:
        import psutil
        pct = psutil.cpu_percent(interval=0.1)
        if pct > cpu_limit:
            return False, f'busy {pct:.0f}%'
    except ImportError:
        if hasattr(os, 'getloadavg'):
            try:
                pct = os.getloadavg()[0] / max(os.cpu_count() or 1, 1) * 100
                if pct > cpu_limit:
                    return False, 'high-load'
            except OSError:
                pass
    return True, 'ready'


def mine_once(url, wallet, max_tries):
    data = request(url + '/v1/template?address=' + wallet['address'])
    status = request(url + '/v1/status')
    if status.get('network') != 'moho-testnet-v1':
        raise RuntimeError('Only moho-testnet-v1 is permitted by this miner')
    if data['block']['network'] != status['network']:
        raise RuntimeError('Template network mismatch')
    try:
        block = find_pow(data['block'], max_tries)
    except TimeoutError:
        return None
    try:
        return request(url + '/v1/block', 'POST', {'block': block})
    except ValueError as e:
        # Stale candidate or competing miner: retry next round.
        print('Block declined:', str(e)[:180], flush=True)
        return None


def main():
    p = argparse.ArgumentParser()
    p.add_argument('--url', default='http://127.0.0.1:18777')
    p.add_argument('--wallet', required=True)
    p.add_argument('--attempts', type=int, default=4096)
    p.add_argument('--interval', type=float, default=120.0)
    p.add_argument('--cooldown', type=float, default=120.0)
    p.add_argument('--cpu-limit', type=float, default=55.0)
    p.add_argument('--once', action='store_true')
    args = p.parse_args()
    if not (1 <= args.attempts <= 250000 and 10 <= args.interval <= 3600 and 10 <= args.cooldown <= 3600 and 1 <= args.cpu_limit <= 100):
        p.error('unsafe resource limits')
    wallet = load_wallet(args.wallet)
    count = 0
    print('MOHO_TESTNET_MINER', platform.node(), wallet['address'], 'attempts', args.attempts, flush=True)
    while True:
        result = None
        ok, reason = should_mine(args.cpu_limit)
        if ok:
            try:
                result = mine_once(args.url.rstrip('/'), wallet, args.attempts)
                if result:
                    count += 1
                    print('MINED', json.dumps({'height': result['height'], 'head': result['head'], 'count': count}), flush=True)
                else:
                    print('No block this burst', flush=True)
            except (OSError, ValueError, TimeoutError, KeyError, urllib.error.URLError, RuntimeError) as e:
                print('Transient:', type(e).__name__, str(e)[:130], flush=True)
        else:
            print('PAUSED', reason, flush=True)
        if args.once:
            break
        time.sleep(args.cooldown if ok and result else args.interval + random.uniform(0, 2))


if __name__ == '__main__':
    main()