"""اجرای آموزش (python train.py) در پس‌زمینه و گزارش وضعیت آن؛ بدون نیاز به torch و FastAPI.

وضعیت فقط در حافظهٔ همین پردازش نگه داشته می‌شود؛ سرور را با یک worker اجرا کنید.
"""
from datetime import datetime, timezone
from pathlib import Path
import os
import re
import subprocess
import sys
import threading

BASE = Path(__file__).resolve().parent
_STEP = re.compile(r'step\s+(\d+)\s*/\s*(\d+)\s*\|\s*loss\s+([0-9.]+)')
_LOSS = re.compile(r'loss\s+(?:به\s+)?([0-9.]+)')


def _now():
    return datetime.now(timezone.utc).isoformat()


class Trainer:
    def __init__(self, cmd=None, cwd: Path = BASE):
        self._cmd = cmd or [sys.executable, '-u', 'train.py']
        self._cwd = cwd
        self._lock = threading.Lock()
        self._state = {'state': 'idle', 'startedAt': None, 'finishedAt': None, 'exitCode': None,
                       'step': None, 'totalSteps': None, 'loss': None, 'lastLine': None}

    def status(self) -> dict:
        with self._lock:
            return dict(self._state)

    def is_running(self) -> bool:
        return self.status()['state'] == 'running'

    def start(self) -> bool:
        """آموزش را شروع می‌کند؛ اگر یکی در حال اجراست False."""
        with self._lock:
            if self._state['state'] == 'running':
                return False
            self._state = {'state': 'running', 'startedAt': _now(), 'finishedAt': None, 'exitCode': None,
                           'step': None, 'totalSteps': None, 'loss': None, 'lastLine': None}
        env = dict(os.environ, PYTHONIOENCODING='utf-8', PYTHONUTF8='1')
        try:
            proc = subprocess.Popen(self._cmd, cwd=str(self._cwd), env=env, stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT)
        except OSError as exc:
            self._finish(-1, f'اجرای آموزش ممکن نشد: {exc}')
            return True
        threading.Thread(target=self._watch, args=(proc,), daemon=True).start()
        return True

    def _finish(self, code, last_line=None):
        with self._lock:
            self._state['state'] = 'succeeded' if code == 0 else 'failed'
            self._state['exitCode'] = code
            self._state['finishedAt'] = _now()
            if last_line:
                self._state['lastLine'] = last_line

    def _watch(self, proc):
        for raw in proc.stdout:
            line = raw.decode('utf-8', errors='replace').strip()
            if not line:
                continue
            with self._lock:
                self._state['lastLine'] = line[:300]
                m = _STEP.search(line)
                if m:
                    self._state['step'], self._state['totalSteps'] = int(m.group(1)), int(m.group(2))
                    self._state['loss'] = float(m.group(3))
                else:
                    m = _LOSS.search(line)
                    if m:
                        self._state['loss'] = float(m.group(1))
        proc.stdout.close()
        self._finish(proc.wait())


trainer = Trainer()
