#!/usr/bin/env python3
"""POST endpoint for Asset Inventory mutations.

Body: JSON {action: "...", ...args}. Every mutation bumps meta.version.
Response: {success, version, ...}

Containers:  add_container, edit_container, delete_container
Categories:  add_category, remove_category   (per-container, custom)
Items:       add_item, edit_item, delete_item
Photos:      add_photo, replace_photo, delete_photo
"""

import base64
import json
import os
import sys
import uuid
from datetime import datetime, timezone

sys.path.insert(0, '/opt/ngon/apps')
sys.path.insert(0, '/var/www/html/ngon/connex')
from managers.auth_manager import AuthManager
from connex_db import get_db, bump_version, next_position, site_names, PHOTO_DIR


def now_iso():
    return datetime.now(timezone.utc).isoformat(timespec='seconds')


def respond(payload):
    print("Content-Type: application/json")
    print("")
    print(json.dumps(payload))


def parse_qty(v):
    try:
        q = int(v)
    except (TypeError, ValueError):
        raise ValueError("quantity required (whole number)")
    if q < 0:
        raise ValueError("quantity cannot be negative")
    return q


def container_dir(container_id):
    return os.path.join(PHOTO_DIR, str(int(container_id)))


def require_container(conn, cid):
    if not conn.execute("SELECT 1 FROM containers WHERE id=?", (cid,)).fetchone():
        raise ValueError("container not found")


def ensure_category(conn, cid, name):
    """Make sure a container has the given category row (create if missing)."""
    name = (name or '').strip()
    if not name:
        raise ValueError("category required")
    row = conn.execute(
        "SELECT 1 FROM container_categories WHERE container_id=? AND name=?", (cid, name)).fetchone()
    if not row:
        pos = next_position(conn, 'container_categories', "WHERE container_id=?", (cid,))
        conn.execute(
            "INSERT OR IGNORE INTO container_categories(container_id, name, position) VALUES (?,?,?)",
            (cid, name, pos))
    return name


def decode_data_url(data_url):
    """Accepts a data URL (data:image/jpeg;base64,....) or bare base64; returns bytes."""
    if not data_url:
        raise ValueError("image data required")
    s = data_url
    if s.startswith('data:'):
        comma = s.find(',')
        if comma == -1:
            raise ValueError("malformed image data")
        s = s[comma + 1:]
    try:
        return base64.b64decode(s)
    except Exception:
        raise ValueError("invalid base64 image data")


def main():
    auth = AuthManager('connex')
    username = auth.is_authenticated()
    if not username or not auth.check_access(username):
        respond({"success": False, "error": "unauthorized"})
        return

    try:
        length = int(os.environ.get('CONTENT_LENGTH') or 0)
    except ValueError:
        length = 0
    raw = sys.stdin.read(length) if length else ''
    try:
        body = json.loads(raw) if raw else {}
    except json.JSONDecodeError:
        respond({"success": False, "error": "invalid json"})
        return

    action = body.get('action')
    if not action:
        respond({"success": False, "error": "missing action"})
        return

    user = username
    conn = get_db()
    try:
        result = {"success": True}
        ts = now_iso()

        # ---------- containers ----------
        if action == 'add_container':
            name = (body.get('name') or '').strip()
            site = (body.get('site') or '').strip()
            ctype = (body.get('type') or '').strip() or 'Connex'
            if not name:
                raise ValueError("name required")
            if site not in site_names():
                raise ValueError(f"unknown site: {site}")
            pos = next_position(conn, 'containers')
            cur = conn.execute(
                "INSERT INTO containers(name, site, type, position, created_by, created_at) "
                "VALUES (?,?,?,?,?,?)", (name, site, ctype, pos, user, ts))
            result['container_id'] = cur.lastrowid

        elif action == 'edit_container':
            cid = int(body['container_id'])
            name = (body.get('name') or '').strip()
            site = (body.get('site') or '').strip()
            ctype = (body.get('type') or '').strip() or 'Connex'
            if not name:
                raise ValueError("name required")
            if site not in site_names():
                raise ValueError(f"unknown site: {site}")
            conn.execute("UPDATE containers SET name=?, site=?, type=? WHERE id=?",
                         (name, site, ctype, cid))

        elif action == 'delete_container':
            cid = int(body['container_id'])
            conn.execute("DELETE FROM containers WHERE id=?", (cid,))  # FK cascades cats+items+photos
            d = container_dir(cid)
            if os.path.isdir(d):
                for fn in os.listdir(d):
                    try:
                        os.remove(os.path.join(d, fn))
                    except OSError:
                        pass
                try:
                    os.rmdir(d)
                except OSError:
                    pass

        # ---------- site ordering ----------
        elif action == 'reorder_sites':
            sites = body.get('sites') or []
            for i, s in enumerate(sites):
                s = (s or '').strip()
                if not s:
                    continue
                conn.execute(
                    "INSERT INTO site_order(site, position) VALUES (?,?) "
                    "ON CONFLICT(site) DO UPDATE SET position=excluded.position",
                    (s, float(i + 1)))

        # ---------- categories (per container) ----------
        elif action == 'add_category':
            cid = int(body['container_id'])
            require_container(conn, cid)
            name = (body.get('name') or '').strip()
            if not name:
                raise ValueError("category name required")
            exists = conn.execute(
                "SELECT 1 FROM container_categories WHERE container_id=? AND name=?", (cid, name)).fetchone()
            if exists:
                raise ValueError("category already exists in this container")
            pos = next_position(conn, 'container_categories', "WHERE container_id=?", (cid,))
            cur = conn.execute(
                "INSERT INTO container_categories(container_id, name, position) VALUES (?,?,?)",
                (cid, name, pos))
            result['category_id'] = cur.lastrowid

        elif action == 'remove_category':
            cid = int(body['container_id'])
            name = (body.get('name') or '').strip()
            if not name:
                raise ValueError("category required")
            # remove the category's photo files, then its rows, then the category itself
            d = container_dir(cid)
            for prow in conn.execute(
                    "SELECT filename FROM photos WHERE container_id=? AND category=?", (cid, name)):
                try:
                    os.remove(os.path.join(d, prow['filename']))
                except OSError:
                    pass
            conn.execute("DELETE FROM photos WHERE container_id=? AND category=?", (cid, name))
            conn.execute("DELETE FROM items  WHERE container_id=? AND category=?", (cid, name))
            conn.execute("DELETE FROM container_categories WHERE container_id=? AND name=?", (cid, name))

        # ---------- items ----------
        elif action == 'add_item':
            cid = int(body['container_id'])
            require_container(conn, cid)
            category = ensure_category(conn, cid, body.get('category'))
            desc = (body.get('description') or '').strip()
            if not desc:
                raise ValueError("description required")
            qty = parse_qty(body.get('quantity'))
            pos = next_position(conn, 'items', "WHERE container_id=?", (cid,))
            cur = conn.execute(
                "INSERT INTO items(container_id, category, description, quantity, position, created_by, created_at) "
                "VALUES (?,?,?,?,?,?,?)", (cid, category, desc, qty, pos, user, ts))
            result['item_id'] = cur.lastrowid

        elif action == 'edit_item':
            iid = int(body['item_id'])
            row = conn.execute("SELECT container_id FROM items WHERE id=?", (iid,)).fetchone()
            if not row:
                raise ValueError("item not found")
            fields, args = [], []
            if 'category' in body:
                fields.append("category=?")
                args.append(ensure_category(conn, row['container_id'], body.get('category')))
            if 'description' in body:
                desc = (body.get('description') or '').strip()
                if not desc:
                    raise ValueError("description required")
                fields.append("description=?"); args.append(desc)
            if 'quantity' in body:
                fields.append("quantity=?"); args.append(parse_qty(body.get('quantity')))
            if not fields:
                raise ValueError("nothing to update")
            args.append(iid)
            conn.execute(f"UPDATE items SET {', '.join(fields)} WHERE id=?", args)

        elif action == 'delete_item':
            conn.execute("DELETE FROM items WHERE id=?", (int(body['item_id']),))

        # ---------- photos ----------
        elif action == 'add_photo':
            cid = int(body['container_id'])
            require_container(conn, cid)
            category = ensure_category(conn, cid, body.get('category'))
            data = decode_data_url(body.get('image_data'))
            d = container_dir(cid)
            os.makedirs(d, exist_ok=True)
            fname = uuid.uuid4().hex + '.jpg'
            with open(os.path.join(d, fname), 'wb') as f:
                f.write(data)
            cur = conn.execute(
                "INSERT INTO photos(container_id, category, filename, orig_name, caption, uploaded_by, uploaded_at) "
                "VALUES (?,?,?,?,?,?,?)",
                (cid, category, fname, (body.get('orig_name') or None),
                 (body.get('caption') or None), user, ts))
            result['photo_id'] = cur.lastrowid

        elif action == 'replace_photo':
            pid = int(body['photo_id'])
            prow = conn.execute("SELECT container_id, filename FROM photos WHERE id=?", (pid,)).fetchone()
            if not prow:
                raise ValueError("photo not found")
            data = decode_data_url(body.get('image_data'))
            d = container_dir(prow['container_id'])
            os.makedirs(d, exist_ok=True)
            new_fname = uuid.uuid4().hex + '.jpg'
            with open(os.path.join(d, new_fname), 'wb') as f:
                f.write(data)
            conn.execute(
                "UPDATE photos SET filename=?, orig_name=?, uploaded_by=?, uploaded_at=? WHERE id=?",
                (new_fname, (body.get('orig_name') or None), user, ts, pid))
            old = os.path.join(d, prow['filename'])
            if os.path.basename(old) != new_fname:
                try:
                    os.remove(old)
                except OSError:
                    pass
            result['photo_id'] = pid

        elif action == 'delete_photo':
            pid = int(body['photo_id'])
            prow = conn.execute("SELECT container_id, filename FROM photos WHERE id=?", (pid,)).fetchone()
            conn.execute("DELETE FROM photos WHERE id=?", (pid,))
            if prow:
                try:
                    os.remove(os.path.join(container_dir(prow['container_id']), prow['filename']))
                except OSError:
                    pass

        else:
            raise ValueError(f"unknown action: {action}")

        result['version'] = bump_version(conn)
        conn.commit()
        respond(result)
    except Exception as e:
        conn.rollback()
        respond({"success": False, "error": str(e)})
    finally:
        conn.close()


if __name__ == "__main__":
    main()
