"""Printedparts models.

PrintedItem is a KIND of 3D-printed part with a quantity on hand - a
consumable, not an ADR-001 asset (which is one row per physical thing).
PrintedItemTransaction is the ledger: every take, restock, and adjust as a
signed quantity change attributed to a badge-resolved employee. The ledger is
the source of truth; quantityonhand is a cache moved in the same commit as
each ledger write, and the stock report reconciles the two.
"""

from datetime import datetime, timezone

from shopdb.api import db, BaseModel


def _utcnow():
    return datetime.now(timezone.utc).replace(tzinfo=None)


TRANSACTION_TYPES = ('take', 'restock', 'adjust')


class PrintedItem(BaseModel):
    """A printable part the engineers stock in bins."""

    __tablename__ = 'printeditems'

    printeditemid = db.Column(db.Integer, primary_key=True)
    itemcode = db.Column(db.String(20), unique=True, index=True,
                         comment='Generated bin-label code, e.g. 3DP-0042')
    gagelabtag = db.Column(db.String(50), unique=True, index=True,
                           comment='Gage-lab assigned asset tag, e.g. WJRP0117')
    itemname = db.Column(db.String(120), nullable=False)
    itemdescription = db.Column(db.String(500))
    imageurl = db.Column(db.String(255))
    quantityonhand = db.Column(db.Integer, nullable=False, default=0)
    lowstockthreshold = db.Column(db.Integer, nullable=False, default=5)
    binlocation = db.Column(db.String(100))
    printnotes = db.Column(db.Text, comment='Material, print time, slicer file')

    transactions = db.relationship(
        'PrintedItemTransaction', backref='printeditem',
        cascade='all, delete-orphan', passive_deletes=True, lazy='dynamic')

    @property
    def islowstock(self):
        return self.quantityonhand <= self.lowstockthreshold

    @property
    def latestrevision(self):
        """Highest print-file revision, or None when the item has no files.
        Stamped on the label QR so the physical part carries its source rev."""
        from sqlalchemy import func
        return db.session.query(func.max(PrintedItemFile.revision)).filter(
            PrintedItemFile.printeditemid == self.printeditemid).scalar()

    def to_dict(self):
        return {
            'printeditemid': self.printeditemid,
            'itemcode': self.itemcode,
            'gagelabtag': self.gagelabtag,
            'latestrevision': self.latestrevision,
            'itemname': self.itemname,
            'itemdescription': self.itemdescription,
            'imageurl': self.imageurl,
            'quantityonhand': self.quantityonhand,
            'lowstockthreshold': self.lowstockthreshold,
            'islowstock': self.islowstock,
            'binlocation': self.binlocation,
            'printnotes': self.printnotes,
            'isactive': self.isactive,
            'createddate': self.createddate.isoformat() + 'Z' if self.createddate else None,
            'modifieddate': self.modifieddate.isoformat() + 'Z' if self.modifieddate else None,
        }


class PrintedItemTransaction(BaseModel):
    """One signed stock movement, always attributed to an employee."""

    __tablename__ = 'printeditemtransactions'

    transactionid = db.Column(db.Integer, primary_key=True)
    printeditemid = db.Column(
        db.Integer,
        db.ForeignKey('printeditems.printeditemid', ondelete='CASCADE'),
        nullable=False, index=True)
    transactiontype = db.Column(db.String(10), nullable=False,
                                comment='take, restock, or adjust')
    quantitychange = db.Column(db.Integer, nullable=False,
                               comment='Negative for take, signed for adjust')
    employeesso = db.Column(db.String(20), nullable=False, index=True)
    employeename = db.Column(db.String(120))
    reason = db.Column(db.String(255))
    # Print-file revision the physical part carried, captured from the label QR
    # at take time (null for manual entry / pre-QR labels). Traceability of
    # which revision was actually consumed.
    revision = db.Column(db.Integer)
    transactiondate = db.Column(db.DateTime, nullable=False, default=_utcnow,
                                index=True)

    def to_dict(self):
        return {
            'transactionid': self.transactionid,
            'printeditemid': self.printeditemid,
            'transactiontype': self.transactiontype,
            'quantitychange': self.quantitychange,
            'employeesso': self.employeesso,
            'employeename': self.employeename,
            'reason': self.reason,
            'revision': self.revision,
            'transactiondate': self.transactiondate.isoformat() + 'Z' if self.transactiondate else None,
        }


class PrintedItemFile(BaseModel):
    """One uploaded revision of an item's print file (STL/3MF/gcode/...).

    Revisions are append-only per item: uploading assigns the next revision
    number and never replaces earlier files, so the history of what was
    actually printed stays reconstructible. The current file is simply the
    highest revision.
    """

    __tablename__ = 'printeditemfiles'

    fileid = db.Column(db.Integer, primary_key=True)
    printeditemid = db.Column(
        db.Integer,
        db.ForeignKey('printeditems.printeditemid', ondelete='CASCADE'),
        nullable=False, index=True)
    revision = db.Column(db.Integer, nullable=False)
    filename = db.Column(db.String(255), nullable=False,
                         comment='Original upload name, used for download')
    storedfilename = db.Column(db.String(191), nullable=False, unique=True,
                               comment='191: unique index fits the 767-byte MySQL prefix')
    filesize = db.Column(db.Integer, nullable=False)
    uploadnote = db.Column(db.String(255),
                           comment='What changed in this revision')
    uploadedby = db.Column(db.String(80), nullable=False,
                           comment='Username of the uploader')

    def to_dict(self):
        return {
            'fileid': self.fileid,
            'printeditemid': self.printeditemid,
            'revision': self.revision,
            'filename': self.filename,
            'filesize': self.filesize,
            'uploadnote': self.uploadnote,
            'uploadedby': self.uploadedby,
            'uploadeddate': self.createddate.isoformat() + 'Z' if self.createddate else None,
        }
