"""A saved set of marker positions, so a bulk change can be undone.

Positions had no history. A landmark transform rewrites every marker on a level
in one statement, and without a way back the honest instruction would be "take a
database backup first" - which nobody does before clicking a button in a UI, so
in practice the feature would either not be used or be used once, badly.

One row holds the whole set as JSON rather than a row per asset. A snapshot is
read back whole or not at all, so restore stays a single statement, and there is
no orphan-child case to reason about.
"""

import json

from shopdb.extensions import db
from .base import BaseModel


class MapPositionSnapshot(BaseModel):
    __tablename__ = 'mappositionsnapshots'

    snapshotid = db.Column(db.Integer, primary_key=True)
    # Which level the operation targeted. Nullable because a snapshot may span
    # levels (moving assets between them), and then no single level owns it.
    levelid = db.Column(db.Integer, nullable=True)
    reason = db.Column(db.String(255), nullable=True)
    assetcount = db.Column(db.Integer, nullable=False, default=0)
    positionsjson = db.Column(db.Text, nullable=False)
    # Set when this snapshot has been restored, so the history reads as what
    # happened rather than as a list of identical-looking saves.
    restoredat = db.Column(db.DateTime, nullable=True)
    createdby = db.Column(db.String(100), nullable=True)

    def __repr__(self):
        return f"<MapPositionSnapshot {self.snapshotid} ({self.assetcount})>"

    @property
    def positions(self):
        try:
            return json.loads(self.positionsjson or '[]')
        except ValueError:
            return []

    def to_dict(self):
        """Metadata only. The positions themselves are large and nobody browsing
        a list of snapshots wants them."""
        data = super().to_dict()
        data.pop('positionsjson', None)
        return data

    @classmethod
    def capture(cls, assets, reason, levelid=None, createdby=None):
        """Record the CURRENT positions of these assets, before they change.

        Includes levelid and mapverifiedat, not just the coordinates: a restore
        has to put a marker back on the level it was on and with the review state
        it had, or undo would silently mark reviewed work as unreviewed.
        """
        rows = [{
            'assetid': asset.assetid,
            'mapx': asset.mapx,
            'mapy': asset.mapy,
            'levelid': asset.levelid,
            'mapverifiedat': asset.mapverifiedat.isoformat()
                             if asset.mapverifiedat else None,
        } for asset in assets]
        snapshot = cls(
            levelid=levelid,
            reason=reason,
            assetcount=len(rows),
            positionsjson=json.dumps(rows, separators=(',', ':')),
            createdby=createdby,
        )
        db.session.add(snapshot)
        db.session.flush()
        return snapshot
