import json
import logging
import os
import signal
import subprocess
import sys
import time
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent
CONFIG_FILE = BASE_DIR / "config.json"
LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(exist_ok=True)

LOG_FILE = LOG_DIR / "miner.log"


logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s",
    handlers=[
        logging.FileHandler(LOG_FILE, encoding="utf-8"),
        logging.StreamHandler(sys.stdout)
    ]
)

logger = logging.getLogger("crypto-miner")

process = None


def load_config():
    if not CONFIG_FILE.exists():
        raise FileNotFoundError(
            f"Config tidak ditemukan: {CONFIG_FILE}"
        )

    with open(CONFIG_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def validate_config(config):
    required = [
        ("miner_path", config.get("miner_path")),
        ("wallet", config.get("wallet")),
        ("worker", config.get("worker")),
    ]

    for name, value in required:
        if not value:
            raise ValueError(f"Konfigurasi '{name}' belum diisi.")

    pool = config.get("pool", {})

    if not pool.get("host"):
        raise ValueError("pool.host belum diisi.")

    if not pool.get("port"):
        raise ValueError("pool.port belum diisi.")


def get_miner_path(config):
    miner_path = Path(config["miner_path"])

    if not miner_path.is_absolute():
        miner_path = BASE_DIR / miner_path

    miner_path = miner_path.resolve()

    if not miner_path.exists():
        raise FileNotFoundError(
            f"Miner tidak ditemukan:\n{miner_path}\n\n"
            "Letakkan xmrig.exe pada folder miner."
        )

    return miner_path


def build_command(config):
    miner_path = get_miner_path(config)

    pool = config["pool"]

    host = pool["host"]
    port = pool["port"]

    wallet = config["wallet"]
    worker = config["worker"]

    command = [
        str(miner_path),

        "--url",
        f"{host}:{port}",

        "--user",
        wallet,

        "--pass",
        worker,

        "--donate-level",
        "1"
    ]

    # TLS
    if pool.get("tls", False):
        command.append("--tls")

    # CPU
    cpu = config.get("cpu", {})

    if cpu.get("enabled", True):
        threads = int(cpu.get("threads", 0))
        max_usage = int(cpu.get("max_usage", 75))

        command.extend([
            "--cpu-max-threads-hint",
            str(max_usage)
        ])

        if threads > 0:
            command.extend([
                "--threads",
                str(threads)
            ])

    else:
        command.append("--no-cpu")

    # NVIDIA
    nvidia = config.get("gpu", {}).get("nvidia", {})

    if nvidia.get("enabled", False):
        logger.info("NVIDIA GPU mining enabled.")

    # AMD
    amd = config.get("gpu", {}).get("amd", {})

    if amd.get("enabled", False):
        logger.info("AMD GPU mining enabled.")

    return command


def start_miner(config):
    global process

    command = build_command(config)

    logger.info("Starting miner...")
    logger.info("Command: %s", " ".join(command))

    creation_flags = 0

    if os.name == "nt":
        creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP

    process = subprocess.Popen(
        command,
        cwd=BASE_DIR,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
        bufsize=1,
        creationflags=creation_flags
    )

    logger.info(
        "Miner started. PID=%s",
        process.pid
    )

    return process


def monitor_process():
    global process

    if process is None:
        return

    while True:
        line = process.stdout.readline()

        if line:
            line = line.rstrip()
            logger.info("[XMRIG] %s", line)

        if process.poll() is not None:
            break


def stop_miner():
    global process

    if process is None:
        return

    if process.poll() is not None:
        process = None
        return

    logger.info("Stopping miner...")

    try:
        if os.name == "nt":
            process.send_signal(signal.CTRL_BREAK_EVENT)
            time.sleep(3)

        if process.poll() is None:
            process.terminate()

        try:
            process.wait(timeout=10)
        except subprocess.TimeoutExpired:
            logger.warning("Miner tidak berhenti. Killing process...")
            process.kill()

    except Exception as exc:
        logger.error(
            "Gagal menghentikan miner: %s",
            exc
        )

    process = None


def shutdown_handler(signum, frame):
    logger.info("Shutdown signal diterima.")

    stop_miner()

    sys.exit(0)


def prompt_manual_input(config):
    """Minta user isi wallet & pool secara manual. Enter kosong = pakai nilai di config.json."""

    print("=" * 70)
    print(" KONFIGURASI MANUAL (kosongkan lalu Enter untuk pakai nilai default)")
    print("=" * 70)

    pool = config.setdefault("pool", {})

    wallet = input(f"Wallet address [{config.get('wallet', '')}]: ").strip()
    if wallet:
        config["wallet"] = wallet

    worker = input(f"Worker name [{config.get('worker', 'windows-pc')}]: ").strip()
    if worker:
        config["worker"] = worker

    host = input(f"Pool host [{pool.get('host', '')}]: ").strip()
    if host:
        pool["host"] = host

    port = input(f"Pool port [{pool.get('port', '')}]: ").strip()
    if port:
        try:
            pool["port"] = int(port)
        except ValueError:
            pool["port"] = port

    tls = input(f"Gunakan TLS? y/n [{'y' if pool.get('tls') else 'n'}]: ").strip().lower()
    if tls in ("y", "yes"):
        pool["tls"] = True
    elif tls in ("n", "no"):
        pool["tls"] = False

    print("=" * 70)

    return config


def main():
    logger.info("=" * 70)
    logger.info("PYTHON CRYPTO MINING CONTROLLER")
    logger.info("=" * 70)

    signal.signal(
        signal.SIGINT,
        shutdown_handler
    )

    signal.signal(
        signal.SIGTERM,
        shutdown_handler
    )

    config = load_config()

    config = prompt_manual_input(config)

    logger.info(
        "Konfigurasi dipakai -> wallet: %s | worker: %s | pool: %s:%s | tls: %s",
        config.get("wallet"),
        config.get("worker"),
        config.get("pool", {}).get("host"),
        config.get("pool", {}).get("port"),
        config.get("pool", {}).get("tls"),
    )

    validate_config(config)

    restart_enabled = config.get(
        "restart",
        {}
    ).get(
        "enabled",
        True
    )

    restart_delay = int(
        config.get(
            "restart",
            {}
        ).get(
            "delay_seconds",
            10
        )
    )

    while True:
        try:
            start_miner(config)

            monitor_process()

            exit_code = process.returncode

            logger.warning(
                "Miner berhenti dengan exit code: %s",
                exit_code
            )

            stop_miner()

            if not restart_enabled:
                break

            logger.info(
                "Restart dalam %s detik...",
                restart_delay
            )

            time.sleep(restart_delay)

        except KeyboardInterrupt:
            stop_miner()
            break

        except Exception as exc:
            logger.exception(
                "Miner error: %s",
                exc
            )

            stop_miner()

            if not restart_enabled:
                break

            time.sleep(restart_delay)

    logger.info("Controller berhenti.")


if __name__ == "__main__":
    main()