# -*- coding: utf-8 -*-
"""KALIBR Cloud API — Python 3.6 + Flask 1.1"""
import os
import uuid
from datetime import datetime

from flask import Flask, jsonify, request
import pymysql

app = Flask(__name__)

DB = {
    "host": "localhost",
    "user": "qsgnoeapbu_kalibr",
    "password": "DenisCrypto1983!",
    "database": "qsgnoeapbu_kalibr",
}

# Fajlovi žive na panel.simicservis.com, NE na api subdomenu.
# API samo čita isti nalog preko diska ili HTTP-a.
PANEL_ORI_URL = "https://panel.simicservis.com/uploads/ori-library"
PANEL_MOD_URL = "https://panel.simicservis.com/uploads/mod-library"

ORI_DISK_CANDIDATES = [
    "/home/qsgnoeapbu/panel.simicservis.com/uploads/ori-library",
    "/home/qsgnoeapbu/panel.simicservis.com/uploads",
    "/home/qsgnoeapbu/public_html/uploads/ori-library",
]
MOD_DISK_CANDIDATES = [
    "/home/qsgnoeapbu/panel.simicservis.com/uploads/mod-library",
    "/home/qsgnoeapbu/panel.simicservis.com/uploads",
    "/home/qsgnoeapbu/public_html/uploads/mod-library",
]


def _first_existing(paths):
    for p in paths:
        if os.path.isdir(p):
            return p
    return paths[0]


LIBRARY_ROOT = _first_existing(ORI_DISK_CANDIDATES)
MOD_LIBRARY_ROOT = _first_existing(MOD_DISK_CANDIDATES)


def db():
    return pymysql.connect(
        host=DB["host"],
        user=DB["user"],
        password=DB["password"],
        database=DB["database"],
        charset="utf8mb4",
        cursorclass=pymysql.cursors.DictCursor,
        autocommit=False,
    )


def garage_from_token():
    token = request.headers.get("X-Garage-Token") or request.args.get("token")
    if not token:
        return None
    conn = db()
    cur = conn.cursor()
    cur.execute(
        "SELECT * FROM garages WHERE sync_token=%s AND active=1 LIMIT 1",
        (token,),
    )
    row = cur.fetchone()
    cur.close()
    conn.close()
    return row


@app.route("/", methods=["GET"])
def root():
    return jsonify(
        name="KALIBR Cloud API",
        status="online",
        owner="panel.simicservis.com",
        python="3.6",
    )


@app.route("/version", methods=["GET"])
@app.route("/version.json", methods=["GET"])
def version():
    return jsonify(
        version="0.7.8",
        url="https://panel.simicservis.com/downloads/kalibr-setup.exe",
        notes="Cloud update check + sync",
        min_os="windows-10",
    )


@app.route("/health", methods=["GET"])
def health():
    try:
        conn = db()
        conn.close()
        return jsonify(
            ok=True,
            db=True,
            panel_ori=PANEL_ORI_URL,
            panel_mod=PANEL_MOD_URL,
            disk_ori=LIBRARY_ROOT,
            disk_ori_exists=os.path.isdir(LIBRARY_ROOT),
            disk_mod=MOD_LIBRARY_ROOT,
            disk_mod_exists=os.path.isdir(MOD_LIBRARY_ROOT),
        )
    except Exception as e:
        return jsonify(ok=False, db=False, error=str(e)), 500


@app.route("/v1/register-device", methods=["POST"])
def register_device():
    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    body = request.get_json(force=True, silent=True) or {}
    hw_id = (body.get("hw_id") or "").strip()
    label = body.get("label") or ""
    if not hw_id:
        return jsonify(error="hw_id_required"), 400
    conn = db()
    cur = conn.cursor()
    did = str(uuid.uuid4())
    cur.execute(
        """
        INSERT INTO devices (id, garage_id, hw_id, label, last_seen)
        VALUES (%s,%s,%s,%s,%s)
        ON DUPLICATE KEY UPDATE label=VALUES(label), last_seen=VALUES(last_seen)
        """,
        (did, g["id"], hw_id, label, datetime.utcnow()),
    )
    conn.commit()
    cur.close()
    conn.close()
    return jsonify(ok=True, garage=g["name"], garage_id=g["id"])


@app.route("/v1/files", methods=["GET"])
def list_files():
    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    conn = db()
    cur = conn.cursor()
    cur.execute(
        """
        SELECT sha256, kind, size_bytes, original_name, brand, ecu_family, sw, hw, remote_path
        FROM file_index WHERE garage_id=%s
        ORDER BY created_at DESC
        LIMIT 20000
        """,
        (g["id"],),
    )
    rows = cur.fetchall()
    cur.close()
    conn.close()
    return jsonify(garage=g["name"], count=len(rows), files=rows)


@app.route("/v1/files/announce", methods=["POST"])
def announce_file():
    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    body = request.get_json(force=True, silent=True) or {}
    sha = (body.get("sha256") or "").lower()
    if len(sha) != 64:
        return jsonify(error="bad_sha"), 400
    conn = db()
    cur = conn.cursor()
    cur.execute(
        "SELECT id FROM file_index WHERE garage_id=%s AND sha256=%s LIMIT 1",
        (g["id"], sha),
    )
    exists = cur.fetchone()
    if exists:
        cur.close()
        conn.close()
        return jsonify(ok=True, exists=True)
    fid = str(uuid.uuid4())
    cur.execute(
        """
        INSERT INTO file_index
          (id, garage_id, sha256, kind, size_bytes, original_name, brand, ecu_family, sw, hw, remote_path, created_at)
        VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
        """,
        (
            fid,
            g["id"],
            sha,
            body.get("kind") or "original",
            int(body.get("size_bytes") or 0),
            body.get("original_name"),
            body.get("brand"),
            body.get("ecu_family"),
            body.get("sw"),
            body.get("hw"),
            body.get("remote_path"),
            datetime.utcnow(),
        ),
    )
    conn.commit()
    cur.close()
    conn.close()
    return jsonify(ok=True, exists=False)


@app.route("/v1/files/missing", methods=["GET", "POST"])
def missing():
    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    body = request.get_json(force=True, silent=True) or {}
    local = set(body.get("sha256") or [])
    conn = db()
    cur = conn.cursor()
    cur.execute("SELECT sha256 FROM file_index WHERE garage_id=%s", (g["id"],))
    remote = set([r["sha256"] for r in cur.fetchall()])
    cur.close()
    conn.close()
    return jsonify(missing=sorted(remote - local), extra_local=len(local - remote))


def _safe_under(root, path):
    root = os.path.abspath(root)
    path = os.path.abspath(path)
    return path.startswith(root + os.sep) or path == root


def _kind_root(kind):
    if kind == "modified":
        return MOD_LIBRARY_ROOT
    return LIBRARY_ROOT


def _kind_url(kind):
    if kind == "modified":
        return PANEL_MOD_URL
    return PANEL_ORI_URL


@app.route("/v1/files/index-disk", methods=["POST"])
def index_disk():
    """Jednom: pročita foldere na hostingu i upiše IMENA u MySQL. Ne računa SHA (predugo)."""
    import hashlib
    import os

    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    added = 0
    existed = 0
    conn = db()
    cur = conn.cursor()
    for kind, root in (("original", LIBRARY_ROOT), ("modified", MOD_LIBRARY_ROOT)):
        if not os.path.isdir(root):
            continue
        for dirpath, _dirs, files in os.walk(root):
            for fn in files:
                low = fn.lower()
                if not low.endswith((".bin", ".ori", ".original", ".hex", ".mod", ".ecu")):
                    continue
                full = os.path.join(dirpath, fn)
                rel = os.path.relpath(full, root).replace("\\", "/")
                try:
                    size = os.path.getsize(full)
                except OSError:
                    continue
                stub = hashlib.sha256(("path:%s:%s:%s" % (kind, rel, size)).encode("utf-8")).hexdigest()
                cur.execute(
                    "SELECT id FROM file_index WHERE garage_id=%s AND (sha256=%s OR (original_name=%s AND remote_path=%s)) LIMIT 1",
                    (g["id"], stub, fn, rel),
                )
                if cur.fetchone():
                    existed += 1
                    continue
                fid = str(uuid.uuid4())
                cur.execute(
                    """
                    INSERT INTO file_index
                      (id, garage_id, sha256, kind, size_bytes, original_name, brand, ecu_family, sw, hw, remote_path, created_at)
                    VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
                    """,
                    (fid, g["id"], stub, kind, size, fn, None, None, None, None, rel, datetime.utcnow()),
                )
                added += 1
                if added % 200 == 0:
                    conn.commit()
    conn.commit()
    cur.close()
    conn.close()
    return jsonify(ok=True, added=added, existed=existed)


@app.route("/v1/files/announce-batch", methods=["POST"])
def announce_batch():
    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    body = request.get_json(force=True, silent=True) or {}
    items = body.get("files") or []
    added = 0
    existed = 0
    conn = db()
    cur = conn.cursor()
    for it in items[:500]:
        sha = (it.get("sha256") or "").lower()
        if len(sha) != 64:
            continue
        cur.execute(
            "SELECT id FROM file_index WHERE garage_id=%s AND sha256=%s LIMIT 1",
            (g["id"], sha),
        )
        if cur.fetchone():
            existed += 1
            continue
        fid = str(uuid.uuid4())
        cur.execute(
            """
            INSERT INTO file_index
              (id, garage_id, sha256, kind, size_bytes, original_name, brand, ecu_family, sw, hw, remote_path, created_at)
            VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
            """,
            (
                fid,
                g["id"],
                sha,
                it.get("kind") or "original",
                int(it.get("size_bytes") or 0),
                it.get("original_name"),
                it.get("brand"),
                it.get("ecu_family"),
                it.get("sw"),
                it.get("hw"),
                it.get("remote_path"),
                datetime.utcnow(),
            ),
        )
        added += 1
    conn.commit()
    cur.close()
    conn.close()
    return jsonify(ok=True, added=added, existed=existed)


@app.route("/v1/files/download", methods=["GET"])
def download_file():
    import os
    from flask import send_file

    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    sha = (request.args.get("sha256") or "").lower()
    name = request.args.get("name") or ""
    conn = db()
    cur = conn.cursor()
    row = None
    if len(sha) == 64:
        cur.execute(
            "SELECT * FROM file_index WHERE garage_id=%s AND sha256=%s LIMIT 1",
            (g["id"], sha),
        )
        row = cur.fetchone()
    if not row and name:
        cur.execute(
            "SELECT * FROM file_index WHERE garage_id=%s AND original_name=%s LIMIT 1",
            (g["id"], name),
        )
        row = cur.fetchone()
    cur.close()
    conn.close()
    if not row:
        return jsonify(error="not_in_index"), 404
    root = _kind_root(row.get("kind") or "original")
    candidates = []
    if row.get("remote_path"):
        candidates.append(os.path.join(root, row["remote_path"].replace("/", os.sep)))
    if row.get("original_name"):
        candidates.append(os.path.join(root, row["original_name"]))
        for dirpath, _d, files in os.walk(root):
            if row["original_name"] in files:
                candidates.append(os.path.join(dirpath, row["original_name"]))
                break
    for p in candidates:
        if os.path.isfile(p) and _safe_under(root, p):
            name = row.get("original_name") or os.path.basename(p)
            try:
                return send_file(p, as_attachment=True, download_name=name)
            except TypeError:
                return send_file(p, as_attachment=True, attachment_filename=name)
    # Disk nije isti folder kao API — skini s panel.simicservis.com
    from flask import redirect
    rel = (row.get("remote_path") or row.get("original_name") or "").replace("\\", "/")
    if not rel:
        return jsonify(error="file_missing_on_disk"), 404
    return redirect(_kind_url(row.get("kind") or "original").rstrip("/") + "/" + rel)


@app.route("/v1/files/upload", methods=["POST"])
def upload_bin():
    """Prima bin sa KALIBR klijenta i snima na panel.simicservis.com/uploads/..."""
    import hashlib
    import re

    g = garage_from_token()
    if not g:
        return jsonify(error="bad_token"), 401
    kind = (request.args.get("kind") or "original").strip()
    if kind not in ("original", "modified"):
        kind = "original"
    name = request.args.get("name") or "file.bin"
    name = os.path.basename(name.replace("\\", "/"))
    name = re.sub(r"[^A-Za-z0-9._\-()+\s]", "_", name)
    if not name:
        name = "file.bin"
    sha = (request.args.get("sha256") or "").lower()
    data = request.get_data(cache=False)
    if not data or len(data) < 64:
        return jsonify(error="empty"), 400
    if len(data) > 40 * 1024 * 1024:
        return jsonify(error="too_large"), 413
    if len(sha) != 64:
        sha = hashlib.sha256(data).hexdigest()
    root = _kind_root(kind)
    if not os.path.isdir(root):
        try:
            os.makedirs(root)
        except Exception as e:
            return jsonify(error="no_upload_dir", path=root, detail=str(e)), 500
    dest = os.path.join(root, name)
    if os.path.isfile(dest) and os.path.getsize(dest) == len(data):
        exists = True
    else:
        tmp = dest + ".part"
        with open(tmp, "wb") as f:
            f.write(data)
        os.rename(tmp, dest)
        exists = False
    rel = name
    conn = db()
    cur = conn.cursor()
    cur.execute(
        "SELECT id FROM file_index WHERE garage_id=%s AND sha256=%s LIMIT 1",
        (g["id"], sha),
    )
    if not cur.fetchone():
        cur.execute(
            """
            INSERT INTO file_index
              (id, garage_id, sha256, kind, size_bytes, original_name, brand, ecu_family, sw, hw, remote_path, created_at)
            VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
            """,
            (
                str(uuid.uuid4()),
                g["id"],
                sha,
                kind,
                len(data),
                name,
                None,
                None,
                None,
                None,
                rel,
                datetime.utcnow(),
            ),
        )
        conn.commit()
    cur.close()
    conn.close()
    return jsonify(ok=True, exists=exists, name=name, sha256=sha, url=_kind_url(kind).rstrip("/") + "/" + rel)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)
