"""AI-owned atomic display policy; not part of Web Start or engine configuration."""
import argparse
import fcntl
import hashlib
import json
import math
import os
from pathlib import Path
import tempfile
import time

ENV = 'CONTEXTRA_FIGHT_DISPLAY_POLICY'


def validate(value):
    if type(value) is not dict or set(value) != {'version', 'hold_seconds'} or type(value['version']) is not int or value['version'] != 1:
        raise ValueError('FIGHT_DISPLAY_POLICY_SCHEMA')
    seconds = value['hold_seconds']
    if type(seconds) not in (int, float) or not math.isfinite(seconds) or not 0 <= seconds <= 5:
        raise ValueError('FIGHT_DISPLAY_HOLD_RANGE')
    return dict(version=1, hold_seconds=float(seconds))


def read(path):
    path = Path(path)
    if not path.is_absolute():
        raise ValueError('FIGHT_DISPLAY_POLICY_PATH')
    if path.is_symlink() or any(p.is_symlink() for p in path.parents):
        raise ValueError('FIGHT_DISPLAY_POLICY_SYMLINK')
    if not path.exists():
        return dict(version=1, hold_seconds=3.)
    if not path.is_file() or path.stat().st_size > 4096:
        raise ValueError('FIGHT_DISPLAY_POLICY_FILE')
    return validate(json.loads(path.read_text()))


def write(path, seconds):
    value = validate(dict(version=1, hold_seconds=seconds))
    path = Path(path)
    if not path.is_absolute() or not path.parent.is_dir():
        raise ValueError('FIGHT_DISPLAY_POLICY_PATH')
    read(path)
    with (path.parent / (path.name + '.lock')).open('a+') as lock:
        fcntl.flock(lock, fcntl.LOCK_EX)
        old = read(path)
        if path.exists() and old == value:
            return value
        fd, name = tempfile.mkstemp(prefix=path.name + '.', dir=path.parent)
        try:
            with os.fdopen(fd, 'w') as f:
                json.dump(value, f); f.flush(); os.fsync(f.fileno())
            os.chmod(name, 0o644)
            os.replace(name, path)
            directory = os.open(path.parent, os.O_DIRECTORY)
            try: os.fsync(directory)
            finally: os.close(directory)
        finally:
            if os.path.exists(name): os.unlink(name)
    return value


class PolicyReader:
    def __init__(self, path, clock=time.monotonic):
        self.path = path
        self.clock = clock
        self.next_read = 0.
        self.value = dict(version=1, hold_seconds=3.)
        self.error = None

    def get(self):
        now = self.clock()
        if now >= self.next_read:
            self.next_read = now + 1.
            try:
                self.value = read(self.path); self.error = None
            except (ValueError, OSError):
                self.error = 'INVALID_POLICY_RETAINED_LAST_GOOD'
        return self.value['hold_seconds']

    def status(self):
        return dict(self.value, revision=hashlib.sha256(json.dumps(self.value, sort_keys=True).encode()).hexdigest()[:12], error=self.error)


def main():
    p = argparse.ArgumentParser()
    p.add_argument('action', choices=('show', 'set'))
    p.add_argument('--path', default=os.environ.get(ENV))
    p.add_argument('--seconds', type=float)
    a = p.parse_args()
    if not a.path: p.error('Policy path is required')
    if a.action == 'set':
        if a.seconds is None: p.error('--seconds is required')
        value = write(a.path, a.seconds)
    else: value = read(a.path)
    print(json.dumps(value))


if __name__ == '__main__':
    main()
