import os,re,subprocess,time,shlex
from pathlib import Path

BASE=Path('/var/lib/scpanel/cron'); ETC=Path('/etc/cron.d')
SAFE=re.compile(r'^[A-Za-z0-9_./:@%+\-=,\s\*\?]+$')
SCHED=re.compile(r'^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$')

def validate_schedule(s):
    s=(s or '').strip()
    if not SCHED.fullmatch(s): raise ValueError('Schedule must contain exactly 5 cron fields.')
    if any(x in s for x in ['\n','\r']): raise ValueError('Invalid schedule.')
    return s

def validate_command(cmd):
    cmd=(cmd or '').strip()
    if not cmd or '\n' in cmd or '\r' in cmd or len(cmd)>4000: raise ValueError('Invalid command.')
    return cmd

def validate_path(p,home):
    p=(p or home).strip()
    q=Path(p).resolve()
    h=Path(home).resolve()
    if q!=h and h not in q.parents: raise ValueError('Working directory must stay inside the hosting home.')
    return str(q)

def paths(cid):
    BASE.mkdir(parents=True,exist_ok=True); return BASE/f'{cid}.out', BASE/f'{cid}.err', ETC/f'scpanel-{cid}'

def install(row):
    cid=row['id']; out,err,conf=paths(cid); home=row['home_dir']; user=row['system_user']
    schedule=validate_schedule(row['schedule']); cmd=validate_command(row['command']); wd=validate_path(row['working_dir'],home)
    out.parent.mkdir(parents=True,exist_ok=True); os.chmod(out.parent,0o750)
    out.touch(exist_ok=True); err.touch(exist_ok=True); os.chmod(out,0o640); os.chmod(err,0o640)
    # cron runs as the hosting system user; logs remain under SCpanel state.
    line=f"{schedule} {user} cd {shlex.quote(wd)} && /bin/sh -c {shlex.quote(cmd)} >> {shlex.quote(str(out))} 2>> {shlex.quote(str(err))}\n"
    tmp=conf.with_suffix('.tmp'); tmp.write_text('# Managed by SCpanel\nSHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n'+line)
    os.chmod(tmp,0o644); os.replace(tmp,conf)
    if not row['enabled']:
        disable(row)
    return str(conf)

def disable(row):
    _,_,conf=paths(row['id'])
    try: conf.unlink()
    except FileNotFoundError: pass

def remove(row):
    disable(row); out,err,_=paths(row['id'])
    for p in (out,err):
        try:p.unlink()
        except FileNotFoundError:pass

def run_now(row,timeout=120):
    home=row['home_dir']; user=row['system_user']; wd=validate_path(row['working_dir'],home); cmd=validate_command(row['command'])
    p=subprocess.run(['runuser','-u',user,'--','/bin/sh','-lc',f'cd {shlex.quote(wd)} && {cmd}'],cwd=home,text=True,capture_output=True,timeout=timeout)
    return p.returncode,p.stdout,p.stderr

def read_output(row,limit=12000):
    out,err,_=paths(row)
    def tail(p):
        try:return p.read_text(errors='replace')[-limit:]
        except Exception:return ''
    return tail(out),tail(err)

def usage(path):
    try:return sum(x.stat().st_size for x in Path(path).rglob('*') if x.is_file())
    except Exception:return 0
