import json
import os
import secrets
from pathlib import Path

import pymysql
from flask import Flask, g, jsonify, render_template, request
from werkzeug.security import check_password_hash, generate_password_hash

BASE_DIR = Path(__file__).resolve().parent


def _load_db_config():
    env = os.environ
    if env.get("DB_HOST"):
        return {
            "host": env.get("DB_HOST"),
            "user": env.get("DB_USER"),
            "password": env.get("DB_PASSWORD"),
            "database": env.get("DB_NAME"),
            "port": int(env.get("DB_PORT", "3306")),
        }

    for candidate in (BASE_DIR / "db_config.json", BASE_DIR.parent / "db_config.json"):
        if candidate.exists():
            data = json.loads(candidate.read_text(encoding="utf-8"))
            return {
                "host": data["host"],
                "user": data["user"],
                "password": data["password"],
                "database": data["database"],
                "port": int(data.get("port", 3306)),
            }

    raise RuntimeError("DB config not found. Set DB_* env vars or db_config.json.")


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


def _ensure_schema():
    conn = _connect()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                CREATE TABLE IF NOT EXISTS users (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    username VARCHAR(100) NOT NULL UNIQUE,
                    password VARCHAR(255) NOT NULL,
                    name VARCHAR(150) DEFAULT '',
                    email VARCHAR(255) DEFAULT '',
                    role VARCHAR(100) NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                ) ENGINE=InnoDB;
                """
            )
            cur.execute("SHOW COLUMNS FROM users")
            columns = {row["Field"] for row in cur.fetchall()}
            if "name" not in columns:
                cur.execute("ALTER TABLE users ADD COLUMN name VARCHAR(150) DEFAULT ''")
            if "email" not in columns:
                cur.execute("ALTER TABLE users ADD COLUMN email VARCHAR(255) DEFAULT ''")
            cur.execute(
                """
                CREATE TABLE IF NOT EXISTS last_user (
                    id TINYINT PRIMARY KEY,
                    last_username VARCHAR(100)
                ) ENGINE=InnoDB;
                """
            )
            cur.execute(
                """
                CREATE TABLE IF NOT EXISTS budgets (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    number INT NOT NULL UNIQUE,
                    client VARCHAR(255) NOT NULL,
                    contact VARCHAR(255) NOT NULL,
                    phone VARCHAR(50) NOT NULL,
                    email VARCHAR(255),
                    `date` DATE NOT NULL,
                    delivery_time VARCHAR(100),
                    payment_method VARCHAR(100),
                    payment_terms VARCHAR(100),
                    proposal_validity VARCHAR(100),
                    status VARCHAR(50) DEFAULT 'Aguardando'
                ) ENGINE=InnoDB;
                """
            )
            cur.execute(
                """
                CREATE TABLE IF NOT EXISTS budget_items (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    budget_id INT NOT NULL,
                    item_code VARCHAR(50) NOT NULL,
                    description VARCHAR(255) NOT NULL,
                    unit VARCHAR(50),
                    qty DECIMAL(10, 2) NOT NULL DEFAULT 0,
                    price DECIMAL(10, 2) NOT NULL DEFAULT 0,
                    CONSTRAINT fk_budget_items_budget
                        FOREIGN KEY (budget_id) REFERENCES budgets(id) ON DELETE CASCADE
                ) ENGINE=InnoDB;
                """
            )
            cur.execute(
                """
                CREATE TABLE IF NOT EXISTS sessions (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    token VARCHAR(128) NOT NULL UNIQUE,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    CONSTRAINT fk_sessions_user
                        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
                ) ENGINE=InnoDB;
                """
            )
            cur.execute(
                """
                CREATE TABLE IF NOT EXISTS user_storage (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    storage_key VARCHAR(200) NOT NULL,
                    storage_value LONGTEXT,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    UNIQUE KEY uk_user_storage (user_id, storage_key),
                    CONSTRAINT fk_storage_user
                        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
                ) ENGINE=InnoDB;
                """
            )

            cur.execute("SELECT COUNT(*) AS total FROM users")
            row = cur.fetchone()
            if row and row["total"] == 0:
                default_user = os.environ.get("DEFAULT_ADMIN_USER", "Henrique")
                default_pass = os.environ.get("DEFAULT_ADMIN_PASSWORD", "0642")
                default_role = os.environ.get("DEFAULT_ADMIN_ROLE", "Administrador")
                cur.execute(
                    """
                    INSERT INTO users (username, password, name, role)
                    VALUES (%s, %s, %s, %s)
                    """,
                    (default_user, generate_password_hash(default_pass), default_user, default_role),
                )
            conn.commit()
    finally:
        conn.close()


app = Flask(__name__, static_folder="static", template_folder="templates")
app.config["SECRET_KEY"] = os.environ.get("PRINTLAB_SECRET", "change-me")


@app.before_request
def _attach_db():
    if "db" not in g:
        g.db = _connect()


@app.teardown_appcontext
def _close_db(_exc):
    db = g.pop("db", None)
    if db:
        db.close()


def _current_user():
    auth = request.headers.get("Authorization", "")
    token = None
    if auth.startswith("Bearer "):
        token = auth.split(" ", 1)[1].strip()
    if not token:
        token = request.cookies.get("printlab_token")
    if not token:
        return None
    with g.db.cursor() as cur:
        cur.execute(
            """
            SELECT u.id, u.username, u.name, u.email, u.role
            FROM sessions s
            JOIN users u ON u.id = s.user_id
            WHERE s.token = %s
            """,
            (token,),
        )
        row = cur.fetchone()
        if row:
            cur.execute("UPDATE sessions SET last_seen = NOW() WHERE token = %s", (token,))
            g.db.commit()
        return row


def _require_user():
    user = _current_user()
    if not user:
        return None, jsonify({"error": "Nao autorizado"}), 401
    return user, None, None


@app.get("/")
def index():
    base_path = os.environ.get("APP_BASE_PATH", "/")
    if not base_path.startswith("/"):
        base_path = f"/{base_path}"
    if not base_path.endswith("/"):
        base_path = f"{base_path}/"
    return render_template("index.html", base_path=base_path)


@app.post("/api/login")
def api_login():
    payload = request.get_json(silent=True) or {}
    username = (payload.get("user") or "").strip()
    password = payload.get("pass") or ""
    if not username or not password:
        return jsonify({"error": "Informe usuario e senha."}), 400

    with g.db.cursor() as cur:
        cur.execute(
            "SELECT id, username, password, name, email, role FROM users WHERE username = %s",
            (username,),
        )
        user = cur.fetchone()
        if not user:
            return jsonify({"error": "Usuario ou senha invalidos."}), 401

        stored = user["password"] or ""
        valid = check_password_hash(stored, password)
        if not valid and stored == password:
            valid = True
            new_hash = generate_password_hash(password)
            cur.execute("UPDATE users SET password = %s WHERE id = %s", (new_hash, user["id"]))
            g.db.commit()

        if not valid:
            return jsonify({"error": "Usuario ou senha invalidos."}), 401

        token = secrets.token_urlsafe(32)
        cur.execute("INSERT INTO sessions (user_id, token) VALUES (%s, %s)", (user["id"], token))
        g.db.commit()

    resp = jsonify(
        {
            "token": token,
            "user": {
                "id": user["id"],
                "username": user["username"],
                "name": user.get("name") or "",
                "email": user.get("email") or "",
                "role": user.get("role") or "",
            },
        }
    )
    resp.set_cookie("printlab_token", token, httponly=True, samesite="Lax", path="/")
    return resp


@app.get("/api/session")
def api_session():
    user, err, status = _require_user()
    if err:
        return err, status
    return jsonify({"user": user})


@app.post("/api/logout")
def api_logout():
    auth = request.headers.get("Authorization", "")
    token = None
    if auth.startswith("Bearer "):
        token = auth.split(" ", 1)[1].strip()
    if not token:
        token = request.cookies.get("printlab_token")
    if token:
        with g.db.cursor() as cur:
            cur.execute("DELETE FROM sessions WHERE token = %s", (token,))
            g.db.commit()
    resp = jsonify({"ok": True})
    resp.delete_cookie("printlab_token", path="/")
    return resp


@app.get("/api/users")
def api_users():
    user, err, status = _require_user()
    if err:
        return err, status
    with g.db.cursor() as cur:
        cur.execute("SELECT username, name, email, role FROM users ORDER BY username")
        users = cur.fetchall()
    return jsonify({"users": users})


@app.post("/api/users")
def api_users_create():
    user, err, status = _require_user()
    if err:
        return err, status
    payload = request.get_json(silent=True) or {}
    username = (payload.get("username") or "").strip()
    password = payload.get("password") or ""
    name = (payload.get("name") or "").strip()
    email = (payload.get("email") or "").strip()
    role = (payload.get("role") or "Atendimento").strip()
    if not username or not password:
        return jsonify({"error": "Usuario e senha sao obrigatorios."}), 400

    with g.db.cursor() as cur:
        cur.execute("SELECT id FROM users WHERE username = %s", (username,))
        if cur.fetchone():
            return jsonify({"error": "Usuario ja existe."}), 409
        cur.execute(
            """
            INSERT INTO users (username, password, name, email, role)
            VALUES (%s, %s, %s, %s, %s)
            """,
            (username, generate_password_hash(password), name, email, role),
        )
        g.db.commit()
    return jsonify({"ok": True})


@app.put("/api/users/<username>")
def api_users_update(username):
    user, err, status = _require_user()
    if err:
        return err, status
    payload = request.get_json(silent=True) or {}
    new_username = (payload.get("username") or username).strip()
    password = (payload.get("password") or "").strip()
    name = (payload.get("name") or "").strip()
    email = (payload.get("email") or "").strip()
    role = (payload.get("role") or "Atendimento").strip()

    with g.db.cursor() as cur:
        if new_username != username:
            cur.execute("SELECT id FROM users WHERE username = %s", (new_username,))
            if cur.fetchone():
                return jsonify({"error": "Usuario ja existe."}), 409
        if password:
            cur.execute(
                """
                UPDATE users
                SET username = %s, password = %s, name = %s, email = %s, role = %s
                WHERE username = %s
                """,
                (new_username, generate_password_hash(password), name, email, role, username),
            )
        else:
            cur.execute(
                """
                UPDATE users
                SET username = %s, name = %s, email = %s, role = %s
                WHERE username = %s
                """,
                (new_username, name, email, role, username),
            )
        if cur.rowcount == 0:
            return jsonify({"error": "Usuario nao encontrado."}), 404
        g.db.commit()
    return jsonify({"ok": True})


@app.get("/api/storage")
def api_storage():
    user, err, status = _require_user()
    if err:
        return err, status
    with g.db.cursor() as cur:
        cur.execute(
            "SELECT storage_key, storage_value FROM user_storage WHERE user_id = %s",
            (user["id"],),
        )
        rows = cur.fetchall()
    items = {row["storage_key"]: row["storage_value"] for row in rows}
    return jsonify({"items": items})


@app.put("/api/storage")
def api_storage_put():
    user, err, status = _require_user()
    if err:
        return err, status
    payload = request.get_json(silent=True) or {}
    items = payload.get("items") or []
    if not isinstance(items, list):
        return jsonify({"error": "Formato invalido."}), 400
    with g.db.cursor() as cur:
        for item in items:
            key = item.get("key")
            value = item.get("value")
            if not key:
                continue
            cur.execute(
                """
                INSERT INTO user_storage (user_id, storage_key, storage_value)
                VALUES (%s, %s, %s)
                ON DUPLICATE KEY UPDATE storage_value = VALUES(storage_value)
                """,
                (user["id"], key, value),
            )
        g.db.commit()
    return jsonify({"ok": True})


@app.delete("/api/storage/<path:key>")
def api_storage_delete(key):
    user, err, status = _require_user()
    if err:
        return err, status
    with g.db.cursor() as cur:
        cur.execute(
            "DELETE FROM user_storage WHERE user_id = %s AND storage_key = %s",
            (user["id"], key),
        )
        g.db.commit()
    return jsonify({"ok": True})


@app.post("/api/storage/clear")
def api_storage_clear():
    user, err, status = _require_user()
    if err:
        return err, status
    with g.db.cursor() as cur:
        cur.execute("DELETE FROM user_storage WHERE user_id = %s", (user["id"],))
        g.db.commit()
    return jsonify({"ok": True})


_ensure_schema()

# cPanel expects "application"
application = app


if __name__ == "__main__":
    app.run(debug=True)
