"""
Flask API untuk Prediksi Permintaan Stok Bahan
Menggunakan Random Forest Model
"""

from flask import Flask, request, jsonify
from flask_cors import CORS
from email.message import EmailMessage
import joblib
import pandas as pd
import numpy as np
import logging
import math
import hashlib
import secrets
import os
import smtplib
from datetime import datetime, timedelta
import mysql.connector
from mysql.connector import Error


def load_env_file():
    """Load simple KEY=VALUE pairs from ml_model/.env if present."""
    env_path = os.path.join(os.path.dirname(__file__), '.env')
    if not os.path.exists(env_path):
        return

    try:
        with open(env_path, 'r', encoding='utf-8') as env_file:
            for raw_line in env_file:
                line = raw_line.strip()
                if not line or line.startswith('#') or '=' not in line:
                    continue

                key, value = line.split('=', 1)
                key = key.strip()
                value = value.strip().strip('"').strip("'")
                if key:
                    os.environ[key] = value
    except OSError as e:
        print(f"Failed to load .env file: {e}")


load_env_file()

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)
CORS(app)

# ============================================================================
# DATABASE CONFIGURATION
# ============================================================================
DB_HOST = 'localhost'
DB_USER = 'root'
DB_PASSWORD = ''  # Ganti dengan password MySQL Anda jika ada
DB_NAME = 'prediksi_stok_db'

# Email/SMTP configuration for OTP delivery.
# For Gmail, use an App Password, not the regular Gmail password.
SMTP_HOST = os.getenv('SMTP_HOST', 'smtp.gmail.com')
SMTP_PORT = int(os.getenv('SMTP_PORT', '587'))
SMTP_EMAIL = os.getenv('SMTP_EMAIL', '')
SMTP_PASSWORD = os.getenv('SMTP_APP_PASSWORD', '').replace(' ', '')
SMTP_SENDER_NAME = os.getenv('SMTP_SENDER_NAME', 'Tobaku Sulastri')

# Table names (use backticks for names with spaces)
TRANSACTIONS_TABLE = "`Stock In`"

def get_db_connection():
    """Get MySQL database connection"""
    try:
        connection = mysql.connector.connect(
            host=DB_HOST,
            user=DB_USER,
            password=DB_PASSWORD,
            database=DB_NAME
        )
        return connection
    except Error as e:
        logger.error(f"Database connection error: {e}")
        return None

def table_exists(connection, table_name: str) -> bool:
    try:
        cursor = connection.cursor()
        cursor.execute("SHOW TABLES LIKE %s", (table_name,))
        exists = cursor.fetchone() is not None
        cursor.close()
        return exists
    except Error:
        return False

def get_product_name_column(connection) -> str:
    cursor = connection.cursor()
    cursor.execute("SHOW COLUMNS FROM products LIKE 'name'")
    has_name = cursor.fetchone() is not None
    cursor.close()
    return 'name' if has_name else 'product_name'

def has_product_unit_column(connection) -> bool:
    cursor = connection.cursor()
    cursor.execute("SHOW COLUMNS FROM products LIKE 'unit'")
    has_unit = cursor.fetchone() is not None
    cursor.close()
    return has_unit

def ensure_product_stock_precision(connection):
    if not table_exists(connection, 'products'):
        return

    stock_col = get_existing_column(connection, 'products', ['current_stock'])
    if not stock_col:
        return

    cursor = connection.cursor(dictionary=True)
    try:
        cursor.execute("SHOW COLUMNS FROM products LIKE 'current_stock'")
        column = cursor.fetchone()
        column_type = (column or {}).get('Type', '').lower()
        if any(kind in column_type for kind in ['decimal', 'float', 'double']):
            return

        cursor.execute(
            "ALTER TABLE products MODIFY current_stock DECIMAL(10,3) NOT NULL DEFAULT 0"
        )
        connection.commit()
    except Error as e:
        logger.warning(f"Could not update current_stock precision: {e}")
    finally:
        cursor.close()

def ensure_product_min_stock_column(connection):
    if not table_exists(connection, 'products'):
        return

    min_col = get_existing_column(connection, 'products', ['min_stock'])
    if min_col:
        return

    cursor = connection.cursor()
    try:
        cursor.execute(
            "ALTER TABLE products ADD COLUMN min_stock DECIMAL(10,3) NOT NULL DEFAULT 0"
        )
        connection.commit()
    except Error as e:
        logger.warning(f"Could not add min_stock column: {e}")
    finally:
        cursor.close()

def backfill_default_min_stock(connection):
    if not table_exists(connection, 'products'):
        return

    min_col = get_existing_column(connection, 'products', ['min_stock'])
    if not min_col:
        return

    name_col = get_product_name_column(connection)
    default_minimums = {
        'Tepung Terigu 1kg': 10,
        'Telur 1kg': 5,
        'Gula Pasir 1kg': 8,
        'Susu Bubuk': 4,
        'Cokelat Bubuk 250gr': 3,
        'Mentega 500gr': 5,
        'Keju Parut 250gr': 2,
        'Baking Powder': 2,
    }

    cursor = connection.cursor()
    try:
        for product_name, min_stock in default_minimums.items():
            cursor.execute(
                f"""
                UPDATE products
                SET {min_col} = %s
                WHERE {name_col} = %s AND COALESCE({min_col}, 0) = 0
                """,
                (min_stock, product_name)
            )
        connection.commit()
    except Error as e:
        logger.warning(f"Could not backfill min_stock defaults: {e}")
    finally:
        cursor.close()

def escape_table_name(table_name: str) -> str:
    return f"`{table_name}`"

def get_existing_table(connection, candidates: list[str]) -> str | None:
    for table_name in candidates:
        if table_exists(connection, table_name):
            return table_name
    return None

def get_existing_column(connection, table_name: str, candidates: list[str]) -> str | None:
    cursor = connection.cursor()
    try:
        for column in candidates:
            cursor.execute(f"SHOW COLUMNS FROM {escape_table_name(table_name)} LIKE %s", (column,))
            if cursor.fetchone() is not None:
                return column
    finally:
        cursor.close()
    return None

def build_report_response(success: bool, message: str, data: list | None = None, status_code: int = 200):
    return jsonify({
        'status': success,
        'message': message,
        'data': data or []
    }), status_code

def verify_login_password(input_password: str, stored_password: str) -> bool:
    """Accept plain text passwords and SHA-256 hashes for simple local auth."""
    if input_password == stored_password:
        return True

    hashed_input = hashlib.sha256(input_password.encode('utf-8')).hexdigest()
    return hashed_input == stored_password

def hash_password(password: str) -> str:
    return hashlib.sha256(password.encode('utf-8')).hexdigest()

def send_otp_email(recipient_email: str, otp_code: str) -> None:
    if not SMTP_EMAIL or not SMTP_PASSWORD:
        raise RuntimeError(
            'SMTP_EMAIL dan SMTP_APP_PASSWORD belum dikonfigurasi'
        )

    message = EmailMessage()
    message['Subject'] = 'Kode OTP Reset Password Tobaku Sulastri'
    message['From'] = f'{SMTP_SENDER_NAME} <{SMTP_EMAIL}>'
    message['To'] = recipient_email
    message.set_content(
        f"""Halo,

Kode OTP untuk reset password aplikasi Tobaku Sulastri adalah:

{otp_code}

Kode ini berlaku selama 10 menit. Abaikan email ini jika Anda tidak meminta reset password.

Terima kasih,
Tobaku Sulastri
"""
    )

    with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=20) as server:
        server.starttls()
        server.login(SMTP_EMAIL, SMTP_PASSWORD)
        server.send_message(message)

def mask_email(email: str) -> str:
    if '@' not in email:
        return email
    name, domain = email.split('@', 1)
    if len(name) <= 2:
        masked_name = name[0] + '*'
    else:
        masked_name = name[:2] + ('*' * max(2, len(name) - 2))
    return f'{masked_name}@{domain}'

def ensure_prediction_needs_column(connection, table_name: str = 'predictions') -> str | None:
    needs_col = get_existing_column(
        connection,
        table_name,
        ['estimated_needs', 'estimasi_kebutuhan_bahan']
    )
    if needs_col:
        return needs_col

    if not table_exists(connection, table_name):
        return None

    cursor = connection.cursor()
    try:
        cursor.execute(
            f"ALTER TABLE {escape_table_name(table_name)} ADD COLUMN estimated_needs TEXT"
        )
        connection.commit()
        return 'estimated_needs'
    except Error as e:
        logger.warning(f"Could not add estimated_needs column: {e}")
        return None
    finally:
        cursor.close()

def ensure_stock_usage_tables(connection):
    cursor = connection.cursor()
    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS stock_usage_history (
                id INT AUTO_INCREMENT PRIMARY KEY,
                recipe_name VARCHAR(255),
                production_quantity INT,
                product_id INT,
                product_name VARCHAR(255) NOT NULL,
                quantity_used FLOAT NOT NULL,
                unit VARCHAR(50),
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """)
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS stok_keluar (
                id INT AUTO_INCREMENT PRIMARY KEY,
                bahan_id INT,
                jumlah_keluar FLOAT NOT NULL,
                satuan VARCHAR(50) DEFAULT 'kg',
                tanggal_keluar DATE NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
        """)
        connection.commit()
    finally:
        cursor.close()

def compute_stock_status(stok: float, stok_minimum: float) -> str:
    if stok > stok_minimum:
        return 'Aman'
    if stok == stok_minimum:
        return 'Rendah'
    return 'Kritis'

def fetch_stock_report(connection):
    # Prioritaskan tabel products agar stok kritis mengikuti data produk aktif.
    table_name = get_existing_table(connection, ['products', 'bahan'])
    if not table_name:
        return None, 'Tabel bahan atau products tidak ditemukan'

    if table_name == 'products':
        ensure_product_stock_precision(connection)
        ensure_product_min_stock_column(connection)
        backfill_default_min_stock(connection)

    name_col = get_existing_column(connection, table_name, ['nama_bahan', 'product_name', 'name'])
    stock_col = get_existing_column(connection, table_name, ['stok', 'current_stock', 'stock'])
    min_col = get_existing_column(connection, table_name, ['stok_minimum', 'min_stock', 'minimum_stock'])
    unit_col = get_existing_column(connection, table_name, ['unit'])

    if not name_col or not stock_col:
        return None, 'Kolom bahan/stok tidak ditemukan'

    select_min = min_col if min_col else '0'
    select_unit = unit_col if unit_col else "''"

    cursor = connection.cursor(dictionary=True)
    cursor.execute(
        f"""
        SELECT {name_col} AS nama_bahan,
               {stock_col} AS stok,
               {select_min} AS stok_minimum,
               {select_unit} AS unit
        FROM {escape_table_name(table_name)}
        ORDER BY {name_col}
        """
    )
    rows = cursor.fetchall()
    cursor.close()

    data = []
    for row in rows:
        stok = float(row.get('stok') or 0)
        stok_minimum = float(row.get('stok_minimum') or 0)
        status = compute_stock_status(stok, stok_minimum)
        data.append({
            'nama_bahan': row.get('nama_bahan'),
            'stok': stok,
            'stok_minimum': stok_minimum,
            'status': status,
            'unit': row.get('unit') or 'kg'
        })

    return data, None


def fetch_penggunaan_bahan(connection):
    table_name = get_existing_table(connection, ['stok_keluar', 'stock_usage_history'])
    if not table_name:
        return []

    cursor = connection.cursor(dictionary=True)

    if table_name == 'stock_usage_history':
        cursor.execute(
            f"""
            SELECT product_name AS nama_bahan,
                   COALESCE(SUM(quantity_used), 0) AS total_digunakan,
                   COALESCE(unit, 'kg') AS satuan
            FROM {escape_table_name(table_name)}
            GROUP BY product_name, unit
            ORDER BY total_digunakan DESC
            LIMIT 5
            """
        )
    else:
        name_col = get_existing_column(connection, table_name, ['nama_bahan'])
        qty_col = get_existing_column(connection, table_name, ['jumlah_keluar', 'quantity_used', 'jumlah'])
        unit_col = get_existing_column(connection, table_name, ['satuan', 'unit'])
        bahan_id_col = get_existing_column(connection, table_name, ['bahan_id', 'product_id'])

        if name_col and qty_col:
            unit_select = unit_col if unit_col else "'kg'"
            cursor.execute(
                f"""
                SELECT {name_col} AS nama_bahan,
                       COALESCE(SUM({qty_col}), 0) AS total_digunakan,
                       {unit_select} AS satuan
                FROM {escape_table_name(table_name)}
                GROUP BY {name_col}, {unit_select}
                ORDER BY total_digunakan DESC
                LIMIT 5
                """
            )
        elif bahan_id_col and qty_col:
            bahan_table = get_existing_table(connection, ['bahan', 'products'])
            if not bahan_table:
                cursor.close()
                return []

            bahan_name_col = get_existing_column(connection, bahan_table, ['nama_bahan', 'product_name', 'name'])
            bahan_unit_col = get_existing_column(connection, bahan_table, ['satuan', 'unit'])
            if not bahan_name_col:
                cursor.close()
                return []

            unit_select = f"b.{bahan_unit_col}" if bahan_unit_col else "'kg'"
            cursor.execute(
                f"""
                SELECT b.{bahan_name_col} AS nama_bahan,
                       COALESCE(SUM(k.{qty_col}), 0) AS total_digunakan,
                       {unit_select} AS satuan
                FROM {escape_table_name(table_name)} k
                JOIN {escape_table_name(bahan_table)} b ON b.id = k.{bahan_id_col}
                GROUP BY b.{bahan_name_col}, {unit_select}
                ORDER BY total_digunakan DESC
                LIMIT 5
                """
            )
        else:
            cursor.close()
            return []

    rows = cursor.fetchall()
    cursor.close()

    data = []
    for row in rows:
        data.append({
            'nama_bahan': row.get('nama_bahan'),
            'total_digunakan': float(row.get('total_digunakan') or 0),
            'satuan': row.get('satuan') or 'kg'
        })

    return data


def getBahanDigunakanHariIni(connection):
    """Total bahan keluar hari ini dari stok_keluar, fallback ke stock_usage_history."""
    table_name = get_existing_table(connection, ['stok_keluar'])
    totalBahanDigunakanHariIni = 0

    if table_name:
        qty_col = get_existing_column(connection, table_name, ['jumlah_keluar'])
        date_col = get_existing_column(connection, table_name, ['tanggal_keluar'])
        created_col = get_existing_column(connection, table_name, ['created_at'])

        if qty_col:
            table_sql = escape_table_name(table_name)
            cursor = connection.cursor()

            if date_col and created_col:
                cursor.execute(
                    f"""
                    SELECT COALESCE(SUM({qty_col}), 0)
                    FROM {table_sql}
                    WHERE (
                        {date_col} >= CURDATE()
                        AND {date_col} < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
                    )
                    OR (
                        {date_col} IS NULL
                        AND {created_col} >= CURDATE()
                        AND {created_col} < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
                    )
                    """
                )
            elif date_col:
                cursor.execute(
                    f"""
                    SELECT COALESCE(SUM({qty_col}), 0)
                    FROM {table_sql}
                    WHERE {date_col} >= CURDATE()
                      AND {date_col} < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
                    """
                )
            elif created_col:
                cursor.execute(
                    f"""
                    SELECT COALESCE(SUM({qty_col}), 0)
                    FROM {table_sql}
                    WHERE {created_col} >= CURDATE()
                      AND {created_col} < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
                    """
                )
            else:
                cursor.close()
                cursor = None

            if cursor:
                totalBahanDigunakanHariIni = cursor.fetchone()[0] or 0
                cursor.close()

    if float(totalBahanDigunakanHariIni or 0) == 0 and table_exists(connection, 'stock_usage_history'):
        cursor = connection.cursor(dictionary=True)
        cursor.execute(
            """
            SELECT quantity_used, unit
            FROM stock_usage_history
            WHERE created_at >= CURDATE()
              AND created_at < DATE_ADD(CURDATE(), INTERVAL 1 DAY)
            """
        )
        rows = cursor.fetchall()
        cursor.close()
        totalBahanDigunakanHariIni = sum(
            convert_stock_quantity(
                float(row.get('quantity_used') or 0),
                row.get('unit') or 'kg',
                'kg'
            )
            for row in rows
        )

    total = float(totalBahanDigunakanHariIni)

    return {
        'total': int(total) if total.is_integer() else total,
        'satuan': 'kg',
        'keterangan': 'total penggunaan hari ini'
    }

def grams_to_kg_rounded(grams: float) -> float:
    """
    Convert grams to kilograms with rounding rules:
    - <= 500g -> 0.5kg
    - 500g < g <= 1000g -> 1kg
    - > 1000g -> round up to nearest 0.5kg
    """
    if grams <= 0:
        raise ValueError("grams must be greater than 0")
    if grams <= 500:
        return 0.5
    if grams <= 1000:
        return 1.0
    return math.ceil(grams / 500.0) * 0.5

def normalize_stock_unit(unit: str | None) -> str:
    normalized = (unit or '').strip().lower()
    if normalized in ['g', 'gr', 'gram', 'grams']:
        return 'gr'
    if normalized in ['kg', 'kilogram', 'kilograms']:
        return 'kg'
    if normalized in ['ml', 'mili', 'mililiter', 'milliliter']:
        return 'ml'
    if normalized in ['l', 'lt', 'ltr', 'liter', 'litre']:
        return 'l'
    if normalized in ['butir', 'pcs', 'piece', 'pieces']:
        return 'butir'
    return normalized

def convert_stock_quantity(quantity: float, from_unit: str, to_unit: str) -> float:
    unit = normalize_stock_unit(from_unit)
    stock_unit = normalize_stock_unit(to_unit)

    if unit == stock_unit or not unit or not stock_unit:
        return quantity
    if unit == 'gr' and stock_unit == 'kg':
        return quantity / 1000
    if unit == 'kg' and stock_unit == 'gr':
        return quantity * 1000
    if unit == 'ml' and stock_unit == 'kg':
        return quantity / 1000
    if unit == 'l' and stock_unit == 'kg':
        return quantity
    if unit == 'kg' and stock_unit == 'ml':
        return quantity * 1000
    if unit == 'kg' and stock_unit == 'l':
        return quantity
    if unit == 'ml' and stock_unit == 'gr':
        return quantity
    if unit == 'gr' and stock_unit == 'ml':
        return quantity
    if unit == 'l' and stock_unit == 'ml':
        return quantity * 1000
    if unit == 'ml' and stock_unit == 'l':
        return quantity / 1000

    return quantity

# ============================================================================
# LOAD MODELS AT STARTUP
# ============================================================================
try:
    model = joblib.load('model_prediksi.pkl')
    encoders = joblib.load('encoders.pkl')
    feature_columns = joblib.load('feature_columns.pkl')
    metadata = joblib.load('model_metadata.pkl')
    logger.info("Models loaded successfully")
    logger.info(f"Model Type: {metadata['model_type']}")
    logger.info(f"R² Score: {metadata['r2_score']:.4f}")
except Exception as e:
    logger.error(f"Failed to load models: {e}")
    raise

# ============================================================================
# ROUTES
# ============================================================================

@app.route('/health', methods=['GET'])
def health():
    """Health check endpoint"""
    return jsonify({
        'status': 'healthy',
        'model_type': metadata['model_type'],
        'r2_score': round(metadata['r2_score'], 4),
        'timestamp': datetime.now().isoformat()
    }), 200


@app.route('/metadata', methods=['GET'])
def get_metadata():
    """Get model metadata"""
    return jsonify({
        'status': 'success',
        'model_info': {
            'type': metadata['model_type'],
            'r2_score': round(metadata['r2_score'], 4),
            'mae': round(metadata['mae'], 4),
            'rmse': round(metadata['rmse'], 4),
            'features': feature_columns,
            'target': metadata['target_column'],
            'total_samples': metadata['total_samples']
        }
    }), 200


@app.route('/api/login', methods=['POST'])
def login():
    """Login using email/username and password stored in the login table."""
    try:
        data = request.json or {}
        username_or_email = (data.get('email') or data.get('username') or '').strip()
        password = data.get('password') or ''

        if not username_or_email or not password:
            return jsonify({
                'status': 'error',
                'message': 'Email/username dan password wajib diisi'
            }), 400

        connection = get_db_connection()
        if connection is None:
            return jsonify({
                'status': 'error',
                'message': 'Tidak dapat terhubung ke database'
            }), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("""
            SELECT id, name, email, username, password
            FROM login
            WHERE email = %s OR username = %s
            LIMIT 1
        """, (username_or_email, username_or_email))
        user = cursor.fetchone()
        cursor.close()
        connection.close()

        if user is None or not verify_login_password(password, user['password']):
            return jsonify({
                'status': 'error',
                'message': 'Email/username atau password salah'
            }), 401

        user.pop('password', None)
        return jsonify({
            'status': 'success',
            'message': 'Login berhasil',
            'data': user
        }), 200
    except Error as e:
        logger.error(f"Login database error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Tabel login belum tersedia atau database bermasalah'
        }), 500
    except Exception as e:
        logger.error(f"Login error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Terjadi kesalahan saat login'
        }), 500


@app.route('/api/forgot-password/send-otp', methods=['POST'])
def send_forgot_password_otp():
    """Create OTP for password reset and send it to the account email."""
    try:
        data = request.json or {}
        username_or_email = (data.get('email') or data.get('username') or '').strip()

        if not username_or_email:
            return jsonify({
                'status': 'error',
                'message': 'Email/username wajib diisi'
            }), 400

        connection = get_db_connection()
        if connection is None:
            return jsonify({
                'status': 'error',
                'message': 'Tidak dapat terhubung ke database'
            }), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("""
            SELECT id, name, email, username
            FROM login
            WHERE email = %s OR username = %s
            LIMIT 1
        """, (username_or_email, username_or_email))
        user = cursor.fetchone()
        cursor.close()
        connection.close()

        if user is None:
            return jsonify({
                'status': 'error',
                'message': 'Akun tidak ditemukan'
            }), 404

        otp_code = f"{secrets.randbelow(1000000):06d}"
        expires_at = datetime.now() + timedelta(minutes=10)

        send_otp_email(user['email'], otp_code)

        connection = get_db_connection()
        if connection is None:
            return jsonify({
                'status': 'error',
                'message': 'Tidak dapat terhubung ke database'
            }), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("""
            INSERT INTO password_reset_otps (login_id, email, otp_code, expires_at)
            VALUES (%s, %s, %s, %s)
        """, (user['id'], user['email'], otp_code, expires_at))

        connection.commit()
        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'message': 'Kode OTP berhasil dikirim ke email. Berlaku selama 10 menit.',
            'data': {
                'email': mask_email(user['email'])
            }
        }), 200
    except RuntimeError as e:
        logger.error(f"Send OTP configuration error: {e}")
        return jsonify({
            'status': 'error',
            'message': str(e)
        }), 500
    except smtplib.SMTPAuthenticationError as e:
        logger.error(f"Send OTP SMTP authentication error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Gagal login ke Gmail. Gunakan App Password Gmail, bukan password email biasa.'
        }), 500
    except smtplib.SMTPException as e:
        logger.error(f"Send OTP SMTP error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Gagal mengirim OTP ke email. Periksa konfigurasi SMTP dan koneksi internet.'
        }), 500
    except Error as e:
        logger.error(f"Send OTP database error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Tabel OTP belum tersedia atau database bermasalah'
        }), 500
    except Exception as e:
        logger.error(f"Send OTP error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Terjadi kesalahan saat membuat OTP'
        }), 500


@app.route('/api/change-password', methods=['POST'])
def change_password():
    """Change password for a logged-in account using the current password."""
    try:
        data = request.json or {}
        username_or_email = (data.get('email') or data.get('username') or '').strip()
        current_password = data.get('current_password') or ''
        new_password = data.get('new_password') or ''

        if not username_or_email or not current_password or not new_password:
            return jsonify({
                'status': 'error',
                'message': 'Email/username, password lama, dan password baru wajib diisi'
            }), 400

        if len(new_password) < 6:
            return jsonify({
                'status': 'error',
                'message': 'Password baru minimal 6 karakter'
            }), 400

        connection = get_db_connection()
        if connection is None:
            return jsonify({
                'status': 'error',
                'message': 'Tidak dapat terhubung ke database'
            }), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("""
            SELECT id, password
            FROM login
            WHERE email = %s OR username = %s
            LIMIT 1
        """, (username_or_email, username_or_email))
        user = cursor.fetchone()

        if user is None:
            cursor.close()
            connection.close()
            return jsonify({
                'status': 'error',
                'message': 'Akun tidak ditemukan'
            }), 404

        if not verify_login_password(current_password, user['password']):
            cursor.close()
            connection.close()
            return jsonify({
                'status': 'error',
                'message': 'Password lama tidak sesuai'
            }), 400

        cursor.execute(
            "UPDATE login SET password = %s WHERE id = %s",
            (hash_password(new_password), user['id'])
        )
        connection.commit()
        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'message': 'Password berhasil diperbarui'
        }), 200
    except Error as e:
        logger.error(f"Change password database error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Database bermasalah saat mengubah password'
        }), 500
    except Exception as e:
        logger.error(f"Change password error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Terjadi kesalahan saat mengubah password'
        }), 500


@app.route('/api/forgot-password/verify-otp', methods=['POST'])
def verify_forgot_password_otp():
    """Verify OTP before allowing password reset."""
    try:
        data = request.json or {}
        username_or_email = (data.get('email') or data.get('username') or '').strip()
        otp_code = (data.get('otp') or '').strip()

        if not username_or_email or not otp_code:
            return jsonify({
                'status': 'error',
                'message': 'Email/username dan OTP wajib diisi'
            }), 400

        connection = get_db_connection()
        if connection is None:
            return jsonify({
                'status': 'error',
                'message': 'Tidak dapat terhubung ke database'
            }), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("""
            SELECT o.id
            FROM password_reset_otps o
            JOIN login l ON l.id = o.login_id
            WHERE (l.email = %s OR l.username = %s)
              AND o.otp_code = %s
              AND o.is_used = 0
              AND o.expires_at >= NOW()
            ORDER BY o.created_at DESC
            LIMIT 1
        """, (username_or_email, username_or_email, otp_code))
        otp = cursor.fetchone()
        cursor.close()
        connection.close()

        if otp is None:
            return jsonify({
                'status': 'error',
                'message': 'OTP salah atau sudah kedaluwarsa'
            }), 400

        return jsonify({
            'status': 'success',
            'message': 'OTP valid'
        }), 200
    except Error as e:
        logger.error(f"Verify OTP database error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Tabel OTP belum tersedia atau database bermasalah'
        }), 500
    except Exception as e:
        logger.error(f"Verify OTP error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Terjadi kesalahan saat verifikasi OTP'
        }), 500


@app.route('/api/forgot-password/reset', methods=['POST'])
def reset_password_with_otp():
    """Reset account password after OTP verification."""
    try:
        data = request.json or {}
        username_or_email = (data.get('email') or data.get('username') or '').strip()
        otp_code = (data.get('otp') or '').strip()
        new_password = data.get('new_password') or ''

        if not username_or_email or not otp_code or not new_password:
            return jsonify({
                'status': 'error',
                'message': 'Email/username, OTP, dan password baru wajib diisi'
            }), 400

        if len(new_password) < 6:
            return jsonify({
                'status': 'error',
                'message': 'Password baru minimal 6 karakter'
            }), 400

        connection = get_db_connection()
        if connection is None:
            return jsonify({
                'status': 'error',
                'message': 'Tidak dapat terhubung ke database'
            }), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("""
            SELECT o.id AS otp_id, l.id AS login_id
            FROM password_reset_otps o
            JOIN login l ON l.id = o.login_id
            WHERE (l.email = %s OR l.username = %s)
              AND o.otp_code = %s
              AND o.is_used = 0
              AND o.expires_at >= NOW()
            ORDER BY o.created_at DESC
            LIMIT 1
        """, (username_or_email, username_or_email, otp_code))
        otp = cursor.fetchone()

        if otp is None:
            cursor.close()
            connection.close()
            return jsonify({
                'status': 'error',
                'message': 'OTP salah atau sudah kedaluwarsa'
            }), 400

        cursor.execute(
            "UPDATE login SET password = %s WHERE id = %s",
            (hash_password(new_password), otp['login_id'])
        )
        cursor.execute(
            "UPDATE password_reset_otps SET is_used = 1 WHERE id = %s",
            (otp['otp_id'],)
        )
        connection.commit()
        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'message': 'Password berhasil diperbarui'
        }), 200
    except Error as e:
        logger.error(f"Reset password database error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Tabel OTP belum tersedia atau database bermasalah'
        }), 500
    except Exception as e:
        logger.error(f"Reset password error: {e}")
        return jsonify({
            'status': 'error',
            'message': 'Terjadi kesalahan saat reset password'
        }), 500


@app.route('/prediksi', methods=['POST'])
def prediksi():
    """
    Predict stock demand

    Body: {
        "tahun": 2024,
        "bulan": 4,
        "hari": 4,
        "hari_dalam_minggu": 3,
        "harga_satuan_update": 50000,
        "total_harga_update": 250000,
        "produk_encoded": 2,
        "nama_produk_encoded": 2,
        "kategori_produk_encoded": 1,
        "hari_minggu": 3
    }
    """
    try:
        data = request.json

        # Validate required fields
        required_fields = feature_columns
        missing_fields = [f for f in required_fields if f not in data]

        if missing_fields:
            return jsonify({
                'status': 'error',
                'message': f'Missing fields: {", ".join(missing_fields)}',
                'required_fields': required_fields
            }), 400

        # Create feature array
        X_pred = np.array([[data[f] for f in feature_columns]])

        # Predict
        prediksi_raw = model.predict(X_pred)[0]
        prediksi = max(1, round(prediksi_raw))

        # Return result
        return jsonify({
            'status': 'success',
            'input': data,
            'prediksi': {
                'jumlah_unit': prediksi,
                'nilai_raw': round(prediksi_raw, 2)
            },
            'model_accuracy': {
                'r2_score': round(metadata['r2_score'], 4),
                'mae': round(metadata['mae'], 4),
                'rmse': round(metadata['rmse'], 4)
            }
        }), 200

    except Exception as e:
        logger.error(f"Prediction error: {str(e)}")
        return jsonify({
            'status': 'error',
            'message': f'Prediction failed: {str(e)}'
        }), 500


@app.route('/batch-prediksi', methods=['POST'])
def batch_prediksi():
    """
    Batch prediction for multiple items

    Body: {
        "items": [
            {"tahun": 2024, "bulan": 4, ...},
            {"tahun": 2024, "bulan": 5, ...}
        ]
    }
    """
    try:
        data = request.json

        if 'items' not in data or not isinstance(data['items'], list):
            return jsonify({
                'status': 'error',
                'message': 'Body harus berisi "items" array'
            }), 400

        results = []

        for i, item in enumerate(data['items']):
            try:
                # Check required fields
                missing_fields = [f for f in feature_columns if f not in item]

                if missing_fields:
                    results.append({
                        'index': i,
                        'status': 'error',
                        'message': f'Missing fields: {", ".join(missing_fields)}'
                    })
                    continue

                # Create feature array
                X_pred = np.array([[item[f] for f in feature_columns]])

                # Predict
                prediksi_raw = model.predict(X_pred)[0]
                prediksi = max(1, round(prediksi_raw))

                results.append({
                    'index': i,
                    'status': 'success',
                    'prediksi': prediksi,
                    'nilai_raw': round(prediksi_raw, 2)
                })

            except Exception as e:
                results.append({
                    'index': i,
                    'status': 'error',
                    'message': str(e)
                })

        return jsonify({
            'status': 'success',
            'total_items': len(data['items']),
            'results': results
        }), 200

    except Exception as e:
        logger.error(f"Batch prediction error: {str(e)}")
        return jsonify({
            'status': 'error',
            'message': f'Batch prediction failed: {str(e)}'
        }), 500


@app.route('/info', methods=['GET'])
def info():
    """Get API information"""
    return jsonify({
        'api_name': 'Prediksi Permintaan Stok Bahan',
        'version': '2.0',
        'model': metadata['model_type'],
        'endpoints': {
            'GET /health': 'API health check',
            'GET /metadata': 'Get model metadata',
            'GET /info': 'Get API info',
            'POST /prediksi': 'Single prediction',
            'POST /batch-prediksi': 'Batch prediction',
            'GET /products': 'Get all products',
            'POST /transactions': 'Save transaction (legacy)',
            'GET /transactions': 'Get transaction history',
            'POST /transaksi': 'Save transaction and update stock automatically',
            'POST /stock/consume': 'Consume stock after production'
        },
        'required_features': feature_columns
    }), 200


# ============================================================================
# DATABASE ENDPOINTS - PRODUCTS & TRANSACTIONS
# ============================================================================

@app.route('/products', methods=['GET'])
def get_products():
    """Get all products from database"""
    try:
        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        ensure_product_stock_precision(connection)
        ensure_product_min_stock_column(connection)
        backfill_default_min_stock(connection)

        cursor = connection.cursor(dictionary=True)
        cursor.execute("SELECT * FROM products ORDER BY name")
        products = cursor.fetchall()
        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'total': len(products),
            'products': products
        }), 200

    except Exception as e:
        logger.error(f"Get products error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/products/<int:product_id>', methods=['GET'])
def get_product(product_id):
    """Get specific product by ID"""
    try:
        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        cursor = connection.cursor(dictionary=True)
        cursor.execute("SELECT * FROM products WHERE id = %s", (product_id,))
        product = cursor.fetchone()
        cursor.close()
        connection.close()

        if not product:
            return jsonify({'status': 'error', 'message': 'Product not found'}), 404

        return jsonify({
            'status': 'success',
            'product': product
        }), 200

    except Exception as e:
        logger.error(f"Get product error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/products', methods=['POST'])
def create_product():
    """
    Create new product in database
    Body: {
        "name": "Tepung Terigu 1kg",
        "category": "Tepung",
        "price": 15000,
        "current_stock": 50,
        "unit": "kg",
        "product_type": "Bahan"
    }
    """
    try:
        data = request.json

        # Validate required fields
        required_fields = ['name', 'category', 'price', 'current_stock']
        missing_fields = [f for f in required_fields if f not in data]

        if missing_fields:
            return jsonify({
                'status': 'error',
                'message': f'Missing fields: {", ".join(missing_fields)}',
                'required_fields': required_fields
            }), 400

        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        ensure_product_stock_precision(connection)
        ensure_product_min_stock_column(connection)

        cursor = connection.cursor()

        # Optional columns compatibility (works for old/new schemas)
        cursor.execute("SHOW COLUMNS FROM products LIKE 'unit'")
        has_unit_column = cursor.fetchone() is not None
        cursor.execute("SHOW COLUMNS FROM products LIKE 'product_type'")
        has_product_type_column = cursor.fetchone() is not None
        cursor.execute("SHOW COLUMNS FROM products LIKE 'min_stock'")
        has_min_stock_column = cursor.fetchone() is not None

        unit_value = data.get('unit')
        product_type_value = data.get('product_type')
        min_stock_value = data.get('min_stock', 0)

        # Check for duplicate product name
        cursor.execute("SELECT id FROM products WHERE name = %s", (data['name'],))
        if cursor.fetchone():
            cursor.close()
            connection.close()
            return jsonify({
                'status': 'error',
                'message': f'Product "{data["name"]}" already exists'
            }), 409

        columns = ['name', 'category', 'price', 'current_stock']
        values = [data['name'], data['category'], data['price'], data['current_stock']]
        if has_unit_column:
            columns.append('unit')
            values.append(unit_value)
        if has_product_type_column:
            columns.append('product_type')
            values.append(product_type_value)
        if has_min_stock_column:
            columns.append('min_stock')
            values.append(min_stock_value)

        column_sql = ', '.join(columns)
        placeholders = ', '.join(['%s'] * len(columns))
        cursor.execute(
            f"INSERT INTO products ({column_sql}) VALUES ({placeholders})",
            tuple(values)
        )
        connection.commit()

        product_id = cursor.lastrowid
        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'product_id': product_id,
            'message': 'Product created successfully'
        }), 201

    except Exception as e:
        logger.error(f"Create product error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/stock/consume', methods=['POST'])
def consume_stock():
    """
    Consume stock after production.
    Body: {
        "recipe_name": "Donat",
        "production_quantity": 10,
        "items": [
            {"product_id": 1, "product_name": "Tepung Terigu 1kg", "quantity": 1, "unit": "kg"}
        ],
        "allow_partial": false
    }
    """
    print("FLASK TERBARU AKTIF")
    print("REQUEST RECEIVED")
    connection = None
    cursor = None
    started_transaction = False
    try:
        data = request.json or {}
        print("REQUEST DATA:", data)
        items = data.get('items', [])
        allow_partial = bool(data.get('allow_partial', False))

        if not isinstance(items, list) or not items:
            return jsonify({
                'status': 'error',
                'message': 'Items wajib diisi'
            }), 400

        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        ensure_product_stock_precision(connection)

        ensure_stock_usage_tables(connection)

        name_column = get_product_name_column(connection)
        unit_column_exists = has_product_unit_column(connection)
        history_enabled = table_exists(connection, 'stock_usage_history')
        stock_out_enabled = table_exists(connection, 'stok_keluar')
        stock_out_product_col = None
        stock_out_qty_col = None
        stock_out_unit_col = None
        stock_out_date_col = None
        if stock_out_enabled:
            stock_out_product_col = get_existing_column(connection, 'stok_keluar', ['bahan_id', 'product_id'])
            stock_out_qty_col = get_existing_column(connection, 'stok_keluar', ['jumlah_keluar'])
            stock_out_unit_col = get_existing_column(connection, 'stok_keluar', ['satuan', 'unit'])
            stock_out_date_col = get_existing_column(connection, 'stok_keluar', ['tanggal_keluar'])
            stock_out_enabled = bool(stock_out_product_col and stock_out_qty_col)

        cursor = connection.cursor(dictionary=True)
        started_transaction = True

        errors = []
        valid_items = []

        for item in items:
            product_id = item.get('product_id')
            product_name = item.get('product_name')
            quantity = item.get('quantity')
            unit = (item.get('unit') or '').strip().lower()

            if not isinstance(quantity, (int, float)) or quantity <= 0:
                errors.append({
                    'item': item,
                    'message': 'Quantity harus lebih dari 0'
                })
                continue

            if unit_column_exists:
                select_fields = f"id, {name_column} as name, current_stock, unit"
            else:
                select_fields = f"id, {name_column} as name, current_stock"

            if product_id:
                cursor.execute(
                    f"SELECT {select_fields} FROM products WHERE id = %s",
                    (product_id,)
                )
            else:
                cursor.execute(
                    f"SELECT {select_fields} FROM products WHERE {name_column} = %s",
                    (product_name,)
                )

            product = cursor.fetchone()
            if not product:
                errors.append({
                    'item': item,
                    'message': 'Produk tidak ditemukan'
                })
                continue

            product_unit = (product.get('unit') or '').strip().lower()
            effective_unit = normalize_stock_unit(product_unit or unit)
            effective_quantity = convert_stock_quantity(
                float(quantity),
                unit or effective_unit,
                effective_unit
            )

            logger.info(
                f"[stock/consume] Using quantity: {quantity} {unit or effective_unit} -> {effective_quantity} {effective_unit} (product_id={product.get('id')})"
            )

            if product['current_stock'] < effective_quantity:
                errors.append({
                    'item': item,
                    'message': 'Stok tidak cukup',
                    'available': product['current_stock']
                })
                continue

            valid_items.append({
                'product': product,
                'quantity': effective_quantity,
                'unit': effective_unit or product.get('unit'),
                'input_quantity': quantity,
                'input_unit': unit or product.get('unit')
            })

        if errors and not allow_partial:
            connection.rollback()
            return jsonify({
                'status': 'error',
                'message': 'Ada item gagal diproses',
                'errors': errors
            }), 400

        print("UPDATING STOCK")
        results = []
        for entry in valid_items:
            product = entry['product']
            quantity = entry['quantity']
            cursor.execute(
                "UPDATE products SET current_stock = current_stock - %s WHERE id = %s",
                (quantity, product['id'])
            )

            if history_enabled:
                cursor.execute(
                    """
                    INSERT INTO stock_usage_history
                    (recipe_name, production_quantity, product_id, product_name, quantity_used, unit)
                    VALUES (%s, %s, %s, %s, %s, %s)
                    """,
                    (
                        data.get('recipe_name'),
                        data.get('production_quantity'),
                        product['id'],
                        product['name'],
                        quantity,
                        entry['unit']
                    )
                )

            if stock_out_enabled:
                columns = [stock_out_product_col, stock_out_qty_col]
                values = [product['id'], quantity]

                if stock_out_unit_col:
                    columns.append(stock_out_unit_col)
                    values.append(entry['unit'])
                if stock_out_date_col:
                    columns.append(stock_out_date_col)
                    values.append(datetime.now().date())

                column_sql = ', '.join(columns)
                placeholders = ', '.join(['%s'] * len(columns))
                cursor.execute(
                    f"INSERT INTO stok_keluar ({column_sql}) VALUES ({placeholders})",
                    tuple(values)
                )

            results.append({
                'product_id': product['id'],
                'product_name': product['name'],
                'quantity_used': quantity,
                'deducted_amount': quantity,
                'unit': entry['unit'],
                'quantity_input': entry.get('input_quantity'),
                'unit_input': entry.get('input_unit')
            })

        connection.commit()

        return jsonify({
            'status': 'success',
            'processed': len(results),
            'results': results,
            'errors': errors if allow_partial else []
        }), 200

    except Exception as e:
        if connection and started_transaction:
            try:
                connection.rollback()
            except Exception:
                logger.exception("[stock/consume] Failed to rollback transaction")
        logger.error(f"Consume stock error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500
    finally:
        if cursor is not None:
            try:
                cursor.close()
            except Exception:
                logger.exception("[stock/consume] Failed to close cursor")
        if connection is not None:
            try:
                connection.close()
            except Exception:
                logger.exception("[stock/consume] Failed to close connection")
        print("REQUEST FINISHED")


@app.route('/transactions', methods=['POST'])
def save_transaction():
    """
    Save transaction to database
    Body: {
        "product_name": "Tepung Terigu 1kg",
        "category": "Tepung",
        "quantity": 5,
        "unit_price": 15000,
        "total_price": 75000,
        "transaction_date": "2024-04-05"
    }
    """
    try:
        data = request.json

        # Validate required fields
        required_fields = ['product_name', 'category', 'quantity', 'unit_price', 'total_price', 'transaction_date']
        missing_fields = [f for f in required_fields if f not in data]

        if missing_fields:
            return jsonify({
                'status': 'error',
                'message': f'Missing fields: {", ".join(missing_fields)}',
                'required_fields': required_fields
            }), 400

        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        cursor = connection.cursor()
        cursor.execute(f"""
            INSERT INTO {TRANSACTIONS_TABLE}
            (product_name, category, quantity, unit_price, total_price, transaction_date)
            VALUES (%s, %s, %s, %s, %s, %s)
        """, (
            data['product_name'],
            data['category'],
            data['quantity'],
            data['unit_price'],
            data['total_price'],
            data['transaction_date']
        ))
        connection.commit()

        transaction_id = cursor.lastrowid
        cursor.close()
        connection.close()

        logger.info(f"Transaction saved: ID={transaction_id}")

        return jsonify({
            'status': 'success',
            'message': 'Transaction saved successfully',
            'transaction_id': transaction_id
        }), 201

    except Exception as e:
        logger.error(f"Save transaction error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/transaksi', methods=['POST'])
def add_transaction_with_stock_update():
    """
    Add transaction AND update stock automatically
    Body: {
        "produk_id": 1,
        "jumlah": 5,
        "unit_price": 15000,
        "total_price": 75000,
        "transaction_date": "2024-04-05"
    }
    """
    try:
        data = request.json

        # Validate required fields
        required_fields = ['produk_id', 'jumlah', 'unit_price', 'total_price', 'transaction_date']
        missing_fields = [f for f in required_fields if f not in data]

        if missing_fields:
            return jsonify({
                'status': 'error',
                'message': f'Missing fields: {", ".join(missing_fields)}',
                'required_fields': required_fields
            }), 400

        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        cursor = connection.cursor(dictionary=True)

        # 1. Get product info
        cursor.execute("SELECT id, name, category FROM products WHERE id = %s", (data['produk_id'],))
        product = cursor.fetchone()

        if not product:
            cursor.close()
            connection.close()
            return jsonify({
                'status': 'error',
                'message': f'Product ID {data["produk_id"]} not found'
            }), 404

        # 2. Save transaction
        cursor.execute(f"""
            INSERT INTO {TRANSACTIONS_TABLE}
            (product_name, category, quantity, unit_price, total_price, transaction_date)
            VALUES (%s, %s, %s, %s, %s, %s)
        """, (
            product['name'],
            product['category'],
            data['jumlah'],
            data['unit_price'],
            data['total_price'],
            data['transaction_date']
        ))
        connection.commit()
        transaction_id = cursor.lastrowid

        # 3. Update product stock (add quantity)
        cursor.execute("""
            UPDATE products
            SET current_stock = current_stock + %s
            WHERE id = %s
        """, (data['jumlah'], data['produk_id']))
        connection.commit()

        cursor.close()
        connection.close()

        logger.info(f"Transaction saved: ID={transaction_id}, Stock updated for product ID={data['produk_id']} (+{data['jumlah']})")

        return jsonify({
            'status': 'success',
            'message': 'Stok berhasil ditambahkan',
            'transaction_id': transaction_id,
            'product_id': data['produk_id'],
            'product_name': product['name'],
            'quantity_added': data['jumlah']
        }), 201

    except Exception as e:
        logger.error(f"Add transaction with stock update error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500



@app.route('/transactions', methods=['GET'])
def get_transactions():
    """Get transaction history"""
    try:
        # Get optional query parameters
        limit = request.args.get('limit', 100, type=int)
        offset = request.args.get('offset', 0, type=int)
        product_name = request.args.get('product_name', None)

        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        cursor = connection.cursor(dictionary=True)

        # Build query
        query = f"SELECT * FROM {TRANSACTIONS_TABLE} WHERE 1=1"
        params = []

        if product_name:
            query += " AND product_name LIKE %s"
            params.append(f"%{product_name}%")

        query += " ORDER BY created_at DESC LIMIT %s OFFSET %s"
        params.extend([limit, offset])

        cursor.execute(query, params)
        transactions = cursor.fetchall()

        # Get total count
        cursor.execute(f"SELECT COUNT(*) as total FROM {TRANSACTIONS_TABLE}" +
                      (" WHERE product_name LIKE %s" if product_name else ""),
                      ([f"%{product_name}%"] if product_name else []))
        total = cursor.fetchone()['total']

        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'total': total,
            'limit': limit,
            'offset': offset,
            'transactions': transactions
        }), 200

    except Exception as e:
        logger.error(f"Get transactions error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/predictions', methods=['POST'])
def save_prediction():
    """
    Save prediction result to database
    Body: {
        "product_name": "Tepung Terigu 1kg",
        "category": "Tepung",
        "unit_price": 15000,
        "prediction_date": "2024-04-05",
        "predicted_quantity": 45,
        "raw_value": 44.8,
        "estimated_total_price": 672000,
        "accuracy_r2": 0.9964,
        "error_mae": 2.51
    }
    """
    try:
        data = request.json

        required_fields = ['product_name', 'category', 'unit_price', 'prediction_date', 'predicted_quantity']
        missing_fields = [f for f in required_fields if f not in data]

        if missing_fields:
            return jsonify({
                'status': 'error',
                'message': f'Missing fields: {", ".join(missing_fields)}'
            }), 400

        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        table_name = 'predictions'
        needs_col = ensure_prediction_needs_column(connection, table_name)

        columns = [
            'product_name',
            'category',
            'unit_price',
            'prediction_date',
            'predicted_quantity',
            'raw_value',
            'estimated_total_price',
            'accuracy_r2',
            'error_mae'
        ]
        values = [
            data['product_name'],
            data['category'],
            data['unit_price'],
            data['prediction_date'],
            data['predicted_quantity'],
            data.get('raw_value'),
            data.get('estimated_total_price'),
            data.get('accuracy_r2'),
            data.get('error_mae')
        ]

        if needs_col:
            columns.append(needs_col)
            values.append(data.get('estimated_needs') or data.get('estimasi_kebutuhan_bahan'))

        cursor = connection.cursor()
        placeholders = ', '.join(['%s'] * len(columns))
        column_sql = ', '.join(columns)
        cursor.execute(
            f"INSERT INTO predictions ({column_sql}) VALUES ({placeholders})",
            tuple(values)
        )
        connection.commit()

        prediction_id = cursor.lastrowid
        cursor.close()
        connection.close()

        logger.info(f"Prediction saved: ID={prediction_id}")

        return jsonify({
            'status': 'success',
            'message': 'Prediction saved successfully',
            'prediction_id': prediction_id
        }), 201

    except Exception as e:
        logger.error(f"Save prediction error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


# ============================================================================
# REPORT ENDPOINTS - LAPORAN
# ============================================================================

@app.route('/laporan/stok', methods=['GET'])
def laporan_stok():
    """Laporan stok bahan dari tabel bahan/products."""
    try:
        connection = get_db_connection()
        if not connection:
            return build_report_response(False, 'Database connection failed', [], 500)

        data, error_message = fetch_stock_report(connection)
        connection.close()

        if error_message:
            return build_report_response(False, error_message, [], 404)

        return build_report_response(True, 'Data berhasil diambil', data, 200)
    except Exception as e:
        logger.error(f"Laporan stok error: {str(e)}")
        return build_report_response(False, f'Gagal mengambil data: {str(e)}', [], 500)


@app.route('/laporan/bahan-kritis', methods=['GET'])
def laporan_bahan_kritis():
    """Laporan bahan kritis/ rendah berdasarkan stok minimum."""
    try:
        connection = get_db_connection()
        if not connection:
            return build_report_response(False, 'Database connection failed', [], 500)

        data, error_message = fetch_stock_report(connection)
        connection.close()

        if error_message:
            return build_report_response(False, error_message, [], 404)

        critical_items = [
            item for item in data
            if item['status'] in ['Rendah', 'Kritis']
        ]

        return build_report_response(True, 'Data berhasil diambil', critical_items, 200)
    except Exception as e:
        logger.error(f"Laporan bahan kritis error: {str(e)}")
        return build_report_response(False, f'Gagal mengambil data: {str(e)}', [], 500)


@app.route('/laporan/stok-masuk', methods=['GET'])
def laporan_stok_masuk():
    """Laporan riwayat stok masuk dari tabel stok_masuk/Stock In/transactions."""
    try:
        connection = get_db_connection()
        if not connection:
            return build_report_response(False, 'Database connection failed', [], 500)

        table_name = get_existing_table(connection, ['stok_masuk', 'Stock In', 'transactions'])
        if not table_name:
            connection.close()
            return build_report_response(False, 'Tabel stok_masuk/transactions tidak ditemukan', [], 404)

        name_col = get_existing_column(connection, table_name, ['nama_bahan', 'product_name', 'name'])
        qty_col = get_existing_column(connection, table_name, ['jumlah', 'quantity'])
        date_col = get_existing_column(connection, table_name, ['tanggal', 'transaction_date', 'created_at'])
        unit_col = get_existing_column(connection, table_name, ['unit'])
        product_id_col = get_existing_column(connection, table_name, ['product_id', 'produk_id', 'bahan_id'])

        if not qty_col or not date_col:
            connection.close()
            return build_report_response(False, 'Kolom stok masuk tidak lengkap', [], 500)

        cursor = connection.cursor(dictionary=True)
        table_sql = escape_table_name(table_name)

        if not name_col and product_id_col:
            product_table = get_existing_table(connection, ['products', 'bahan'])
            if product_table:
                product_name_col = get_existing_column(connection, product_table, ['nama_bahan', 'product_name', 'name'])
                product_unit_col = get_existing_column(connection, product_table, ['unit'])
                if product_name_col:
                    cursor.execute(
                        f"""
                        SELECT p.{product_name_col} AS nama_bahan,
                               sm.{qty_col} AS jumlah,
                               sm.{date_col} AS tanggal,
                               p.{product_unit_col} AS unit
                        FROM {table_sql} sm
                        JOIN {escape_table_name(product_table)} p ON p.id = sm.{product_id_col}
                        ORDER BY sm.{date_col} DESC
                        """
                    )
                else:
                    cursor.execute(
                        f"""
                        SELECT sm.{qty_col} AS jumlah,
                               sm.{date_col} AS tanggal
                        FROM {table_sql} sm
                        ORDER BY sm.{date_col} DESC
                        """
                    )
            else:
                cursor.execute(
                    f"""
                    SELECT sm.{qty_col} AS jumlah,
                           sm.{date_col} AS tanggal
                    FROM {table_sql} sm
                    ORDER BY sm.{date_col} DESC
                    """
                )
        else:
            unit_select = unit_col if unit_col else "''"
            cursor.execute(
                f"""
                SELECT {name_col} AS nama_bahan,
                       {qty_col} AS jumlah,
                       {date_col} AS tanggal,
                       {unit_select} AS unit
                FROM {table_sql}
                ORDER BY {date_col} DESC
                """
            )

        rows = cursor.fetchall()
        cursor.close()
        connection.close()

        data = []
        for row in rows:
            tanggal = row.get('tanggal')
            data.append({
                'nama_bahan': row.get('nama_bahan'),
                'jumlah': float(row.get('jumlah') or 0),
                'tanggal': tanggal.isoformat() if hasattr(tanggal, 'isoformat') else tanggal,
                'unit': row.get('unit') or 'kg'
            })

        return build_report_response(True, 'Data berhasil diambil', data, 200)
    except Exception as e:
        logger.error(f"Laporan stok masuk error: {str(e)}")
        return build_report_response(False, f'Gagal mengambil data: {str(e)}', [], 500)


@app.route('/laporan/prediksi', methods=['GET'])
def laporan_prediksi():
    """Laporan prediksi permintaan dari tabel prediksi/predictions."""
    try:
        connection = get_db_connection()
        if not connection:
            return build_report_response(False, 'Database connection failed', [], 500)

        table_name = get_existing_table(connection, ['prediksi', 'predictions'])
        if not table_name:
            connection.close()
            return build_report_response(False, 'Tabel prediksi/predictions tidak ditemukan', [], 404)

        name_col = get_existing_column(connection, table_name, ['nama_produk', 'product_name', 'name'])
        result_col = get_existing_column(connection, table_name, ['hasil_prediksi', 'predicted_quantity'])
        estimate_col = get_existing_column(connection, table_name, ['estimasi_kebutuhan_bahan', 'estimated_needs', 'raw_value'])
        date_col = get_existing_column(connection, table_name, ['tanggal_prediksi', 'prediction_date', 'created_at'])

        if not result_col or not date_col:
            connection.close()
            return build_report_response(False, 'Kolom prediksi tidak lengkap', [], 500)

        cursor = connection.cursor(dictionary=True)
        table_sql = escape_table_name(table_name)
        estimate_select = estimate_col if estimate_col else 'NULL'
        name_select = name_col if name_col else "''"

        cursor.execute(
            f"""
            SELECT {name_select} AS nama_produk,
                   {result_col} AS hasil_prediksi,
                   {estimate_select} AS estimasi_kebutuhan_bahan,
                   {date_col} AS tanggal_prediksi
            FROM {table_sql}
            ORDER BY {date_col} DESC
            """
        )

        rows = cursor.fetchall()
        cursor.close()
        connection.close()

        data = []
        for row in rows:
            tanggal_prediksi = row.get('tanggal_prediksi')
            data.append({
                'nama_produk': row.get('nama_produk'),
                'hasil_prediksi': float(row.get('hasil_prediksi') or 0),
                'estimasi_kebutuhan_bahan': row.get('estimasi_kebutuhan_bahan'),
                'tanggal_prediksi': tanggal_prediksi.isoformat() if hasattr(tanggal_prediksi, 'isoformat') else tanggal_prediksi
            })

        return build_report_response(True, 'Data berhasil diambil', data, 200)
    except Exception as e:
        logger.error(f"Laporan prediksi error: {str(e)}")
        return build_report_response(False, f'Gagal mengambil data: {str(e)}', [], 500)


# ============================================================================
# DASHBOARD ENDPOINTS
# ============================================================================

@app.route('/api/dashboard/bahan-digunakan-hari-ini', methods=['GET'])
def bahanDigunakanHariIni():
    """Total bahan keluar hari ini dari stok_keluar."""
    connection = None
    try:
        connection = get_db_connection()
        if not connection:
            return jsonify({
                'status': False,
                'message': 'Database connection failed',
                'total': 0,
                'satuan': 'kg',
                'keterangan': 'total penggunaan hari ini'
            }), 500

        data = getBahanDigunakanHariIni(connection)
        return jsonify(data), 200
    except Exception as e:
        logger.error(f"Bahan digunakan hari ini error: {str(e)}")
        return jsonify({
            'status': False,
            'message': f'Gagal mengambil data: {str(e)}',
            'total': 0,
            'satuan': 'kg',
            'keterangan': 'total penggunaan hari ini'
        }), 500
    finally:
        if connection:
            connection.close()

@app.route('/api/dashboard/summary', methods=['GET'])
def dashboard_summary():
    """Ringkasan dashboard untuk grafik penggunaan bahan."""
    try:
        connection = get_db_connection()
        if not connection:
            return jsonify({
                'status': False,
                'message': 'Database connection failed',
                'penggunaan_bahan': []
            }), 500

        penggunaan = fetch_penggunaan_bahan(connection)
        connection.close()

        return jsonify({
            'status': True,
            'penggunaan_bahan': penggunaan
        }), 200
    except Exception as e:
        logger.error(f"Dashboard summary error: {str(e)}")
        return jsonify({
            'status': False,
            'message': f'Gagal mengambil data: {str(e)}',
            'penggunaan_bahan': []
        }), 500


# ============================================================================
# RECIPES ENDPOINTS
# ============================================================================

@app.route('/recipes', methods=['GET'])
def get_recipes():
    """Get all recipes with their ingredients"""
    try:
        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        cursor = connection.cursor(dictionary=True)

        # Get all recipes
        cursor.execute("SELECT id, recipe_name, description FROM recipes ORDER BY recipe_name")
        recipes = cursor.fetchall()

        # Get ingredients for each recipe
        for recipe in recipes:
            cursor.execute("""
                SELECT product_name, quantity_needed, unit
                FROM recipe_ingredients
                WHERE recipe_id = %s
                ORDER BY product_name
            """, (recipe['id'],))
            recipe['ingredients'] = cursor.fetchall()

        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'total': len(recipes),
            'recipes': recipes
        }), 200

    except Exception as e:
        logger.error(f"Get recipes error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/recipes/<int:recipe_id>', methods=['GET'])
def get_recipe(recipe_id):
    """Get specific recipe with ingredients"""
    try:
        connection = get_db_connection()
        if not connection:
            return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500

        cursor = connection.cursor(dictionary=True)

        # Get recipe
        cursor.execute("SELECT id, recipe_name, description FROM recipes WHERE id = %s", (recipe_id,))
        recipe = cursor.fetchone()

        if not recipe:
            cursor.close()
            connection.close()
            return jsonify({'status': 'error', 'message': 'Recipe not found'}), 404

        # Get ingredients
        cursor.execute("""
            SELECT product_name, quantity_needed, unit
            FROM recipe_ingredients
            WHERE recipe_id = %s
            ORDER BY product_name
        """, (recipe_id,))
        recipe['ingredients'] = cursor.fetchall()

        cursor.close()
        connection.close()

        return jsonify({
            'status': 'success',
            'recipe': recipe
        }), 200

    except Exception as e:
        logger.error(f"Get recipe error: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.errorhandler(404)
def not_found(error):
    return jsonify({'status': 'error', 'message': 'Endpoint tidak ditemukan'}), 404


@app.errorhandler(500)
def internal_error(error):
    return jsonify({'status': 'error', 'message': 'Internal server error'}), 500


@app.route('/test')
def test():
    return {"message": "FLASK BARU AKTIF"}


# ============================================================================
# MAIN
# ============================================================================
if __name__ == '__main__':
    logger.info("=" * 80)
    logger.info("Starting Prediksi Stok API")
    logger.info("=" * 80)
    logger.info(f"Model: {metadata['model_type']}")
    logger.info(f"Accuracy (R²): {metadata['r2_score']:.4f}")
    logger.info(f"Features: {len(feature_columns)}")
    logger.info("Endpoints: /health, /metadata, /info, /prediksi, /batch-prediksi, /products, /transactions, /predictions, /recipes")
    logger.info("Access API at: http://localhost:5000")
    logger.info("=" * 80)

    app.run(
        debug=False,
        host='0.0.0.0',
        port=5000,
        threaded=True
    )
