from __future__ import annotations

import hashlib
import hmac
import os
import secrets
import sqlite3
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent
PROJECT_DIR = BASE_DIR.parent
WEB_DATA_DIR = PROJECT_DIR / "cv_web" / "data"
DB_PATH = WEB_DATA_DIR / "confivoice.db"

DB_DRIVER = os.getenv("CONFIVOICE_DB_DRIVER", "mysql").lower()
MYSQL_HOST = os.getenv("CONFIVOICE_MYSQL_HOST", "127.0.0.1")
MYSQL_PORT = int(os.getenv("CONFIVOICE_MYSQL_PORT", "3306"))
MYSQL_USER = os.getenv("CONFIVOICE_MYSQL_USER", "root")
MYSQL_PASSWORD = os.getenv("CONFIVOICE_MYSQL_PASSWORD", "")
MYSQL_DATABASE = os.getenv("CONFIVOICE_MYSQL_DATABASE", "confivoice")
PASSWORD_HASH_ITERATIONS = 120_000


def hash_password(password):
    salt = secrets.token_hex(16)
    digest = hashlib.pbkdf2_hmac(
        "sha256",
        password.encode("utf-8"),
        salt.encode("utf-8"),
        PASSWORD_HASH_ITERATIONS,
    ).hex()
    return f"pbkdf2_sha256${PASSWORD_HASH_ITERATIONS}${salt}${digest}"


def verify_password(password, stored_hash):
    try:
        algorithm, iterations, salt, digest = stored_hash.split("$", 3)
        if algorithm != "pbkdf2_sha256":
            return False
        candidate = hashlib.pbkdf2_hmac(
            "sha256",
            password.encode("utf-8"),
            salt.encode("utf-8"),
            int(iterations),
        ).hex()
        return hmac.compare_digest(candidate, digest)
    except Exception:
        return False


def get_database_label():
    if DB_DRIVER == "mysql":
        return f"mysql://{MYSQL_USER}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
    return str(DB_PATH)


def get_connection():
    if DB_DRIVER == "mysql":
        return get_mysql_connection()

    WEB_DATA_DIR.mkdir(parents=True, exist_ok=True)
    connection = sqlite3.connect(DB_PATH)
    connection.row_factory = sqlite3.Row
    return connection


def get_mysql_connection(database=MYSQL_DATABASE):
    try:
        import pymysql
    except ImportError as error:
        raise RuntimeError("PyMySQL belum terinstall. Jalankan: pip install PyMySQL") from error

    return pymysql.connect(
        host=MYSQL_HOST,
        port=MYSQL_PORT,
        user=MYSQL_USER,
        password=MYSQL_PASSWORD,
        database=database,
        charset="utf8mb4",
        cursorclass=pymysql.cursors.DictCursor,
        autocommit=False,
    )


def init_mysql_db():
    with get_mysql_connection(database=None) as connection:
        with connection.cursor() as cursor:
            cursor.execute(
                f"CREATE DATABASE IF NOT EXISTS `{MYSQL_DATABASE}` "
                "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
            )
        connection.commit()

    with get_connection() as connection:
        with connection.cursor() as cursor:
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS prediction_results (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NULL,
                    student_name VARCHAR(255) NOT NULL,
                    student_gender VARCHAR(20),
                    predicted_label VARCHAR(20),
                    description VARCHAR(255),
                    confidence DOUBLE NOT NULL DEFAULT 0,
                    probability_pd DOUBLE NOT NULL DEFAULT 0,
                    probability_tpd DOUBLE NOT NULL DEFAULT 0,
                    is_valid_audio TINYINT(1) NOT NULL DEFAULT 1,
                    error_message TEXT,
                    audio_duration DOUBLE NOT NULL DEFAULT 0,
                    volume_score DOUBLE NOT NULL DEFAULT 0,
                    intonation_score DOUBLE NOT NULL DEFAULT 0,
                    pause_score DOUBLE NOT NULL DEFAULT 0,
                    speech_activity_ratio DOUBLE NOT NULL DEFAULT 0,
                    silence_ratio DOUBLE NOT NULL DEFAULT 0,
                    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                )
                """
            )
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS users (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    full_name VARCHAR(255) NOT NULL,
                    username VARCHAR(120) NOT NULL UNIQUE,
                    password_hash VARCHAR(255) NOT NULL,
                    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                )
                """
            )
            cursor.execute(
                """
                SELECT COUNT(*) AS total
                FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = %s
                  AND TABLE_NAME = 'prediction_results'
                  AND COLUMN_NAME = 'user_id'
                """,
                (MYSQL_DATABASE,),
            )
            if cursor.fetchone()["total"] == 0:
                cursor.execute("ALTER TABLE prediction_results ADD COLUMN user_id INT NULL AFTER id")
            cursor.execute(
                """
                SELECT COUNT(*) AS total
                FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = %s
                  AND TABLE_NAME = 'prediction_results'
                  AND COLUMN_NAME = 'student_gender'
                """,
                (MYSQL_DATABASE,),
            )
            if cursor.fetchone()["total"] == 0:
                cursor.execute(
                    "ALTER TABLE prediction_results "
                    "ADD COLUMN student_gender VARCHAR(20) NULL AFTER student_name"
                )
        connection.commit()


def init_db():
    if DB_DRIVER == "mysql":
        init_mysql_db()
        return

    with get_connection() as connection:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS prediction_results (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER,
                student_name TEXT NOT NULL,
                student_gender TEXT,
                predicted_label TEXT,
                description TEXT,
                confidence REAL NOT NULL DEFAULT 0,
                probability_pd REAL NOT NULL DEFAULT 0,
                probability_tpd REAL NOT NULL DEFAULT 0,
                is_valid_audio INTEGER NOT NULL DEFAULT 1,
                error_message TEXT,
                audio_duration REAL NOT NULL DEFAULT 0,
                volume_score REAL NOT NULL DEFAULT 0,
                intonation_score REAL NOT NULL DEFAULT 0,
                pause_score REAL NOT NULL DEFAULT 0,
                speech_activity_ratio REAL NOT NULL DEFAULT 0,
                silence_ratio REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
            )
            """
        )
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                full_name TEXT NOT NULL,
                username TEXT NOT NULL UNIQUE,
                password_hash TEXT NOT NULL,
                created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
            )
            """
        )
        columns = {
            row["name"]
            for row in connection.execute("PRAGMA table_info(prediction_results)").fetchall()
        }
        if "user_id" not in columns:
            connection.execute("ALTER TABLE prediction_results ADD COLUMN user_id INTEGER")
        if "student_gender" not in columns:
            connection.execute(
                "ALTER TABLE prediction_results ADD COLUMN student_gender TEXT"
            )
        connection.commit()


def _clean_username(username):
    return username.strip().lower()


def _clean_full_name(full_name):
    return " ".join(full_name.strip().split())


def _user_row_to_dict(row):
    if not row:
        return None
    return {
        "id": row["id"],
        "full_name": row["full_name"],
        "username": row["username"],
        "created_at": str(row["created_at"]),
    }


def create_user(full_name, username, password):
    init_db()
    clean_full_name = _clean_full_name(full_name)
    clean_username = _clean_username(username)
    password_hash = hash_password(password)

    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            try:
                with connection.cursor() as cursor:
                    cursor.execute(
                        """
                        INSERT INTO users (full_name, username, password_hash)
                        VALUES (%s, %s, %s)
                        """,
                        (clean_full_name, clean_username, password_hash),
                    )
                    user_id = cursor.lastrowid
                connection.commit()
            except Exception:
                connection.rollback()
                raise
        return get_user_by_id(user_id)

    with get_connection() as connection:
        cursor = connection.execute(
            """
            INSERT INTO users (full_name, username, password_hash)
            VALUES (?, ?, ?)
            """,
            (clean_full_name, clean_username, password_hash),
        )
        connection.commit()
        return get_user_by_id(cursor.lastrowid)


def get_user_by_username(username):
    init_db()
    clean_username = _clean_username(username)

    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute(
                    """
                    SELECT id, full_name, username, password_hash, created_at
                    FROM users
                    WHERE username = %s
                    """,
                    (clean_username,),
                )
                return cursor.fetchone()

    with get_connection() as connection:
        row = connection.execute(
            """
            SELECT id, full_name, username, password_hash, created_at
            FROM users
            WHERE username = ?
            """,
            (clean_username,),
        ).fetchone()
    return dict(row) if row else None


def get_user_by_id(user_id):
    init_db()

    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute(
                    """
                    SELECT id, full_name, username, created_at
                    FROM users
                    WHERE id = %s
                    """,
                    (user_id,),
                )
                return _user_row_to_dict(cursor.fetchone())

    with get_connection() as connection:
        row = connection.execute(
            """
            SELECT id, full_name, username, created_at
            FROM users
            WHERE id = ?
            """,
            (user_id,),
        ).fetchone()
    return _user_row_to_dict(dict(row) if row else None)


def list_users(limit=500):
    init_db()
    safe_limit = max(1, min(int(limit or 500), 1000))

    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute(
                    """
                    SELECT id, full_name, username, created_at
                    FROM users
                    ORDER BY created_at DESC, id DESC
                    LIMIT %s
                    """,
                    (safe_limit,),
                )
                rows = cursor.fetchall()
        return [_user_row_to_dict(row) for row in rows]

    with get_connection() as connection:
        rows = connection.execute(
            """
            SELECT id, full_name, username, created_at
            FROM users
            ORDER BY created_at DESC, id DESC
            LIMIT ?
            """,
            (safe_limit,),
        ).fetchall()
    return [_user_row_to_dict(dict(row)) for row in rows]


def update_user(user_id, full_name, username, password=None):
    init_db()
    clean_full_name = _clean_full_name(full_name)
    clean_username = _clean_username(username)
    clean_password = (password or "").strip()

    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            try:
                with connection.cursor() as cursor:
                    if clean_password:
                        cursor.execute(
                            """
                            UPDATE users
                            SET full_name = %s, username = %s, password_hash = %s
                            WHERE id = %s
                            """,
                            (
                                clean_full_name,
                                clean_username,
                                hash_password(clean_password),
                                user_id,
                            ),
                        )
                    else:
                        cursor.execute(
                            """
                            UPDATE users
                            SET full_name = %s, username = %s
                            WHERE id = %s
                            """,
                            (clean_full_name, clean_username, user_id),
                        )
                    affected = cursor.rowcount
                connection.commit()
            except Exception:
                connection.rollback()
                raise
        return affected > 0

    with get_connection() as connection:
        if clean_password:
            cursor = connection.execute(
                """
                UPDATE users
                SET full_name = ?, username = ?, password_hash = ?
                WHERE id = ?
                """,
                (clean_full_name, clean_username, hash_password(clean_password), user_id),
            )
        else:
            cursor = connection.execute(
                """
                UPDATE users
                SET full_name = ?, username = ?
                WHERE id = ?
                """,
                (clean_full_name, clean_username, user_id),
            )
        connection.commit()
        return cursor.rowcount > 0


def delete_user(user_id):
    init_db()

    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
                affected = cursor.rowcount
            connection.commit()
        return affected > 0

    with get_connection() as connection:
        cursor = connection.execute("DELETE FROM users WHERE id = ?", (user_id,))
        connection.commit()
        return cursor.rowcount > 0


def authenticate_user(username, password):
    row = get_user_by_username(username)
    if not row or not verify_password(password, row["password_hash"]):
        return None
    return _user_row_to_dict(row)


def save_prediction_result(student_name, result):
    init_db()
    audio_quality = result.get("audio_quality") or {}
    indicators = result.get("voice_indicators") or {}

    values = (
        result.get("user_id"),
        student_name,
        result.get("student_gender"),
        result.get("predicted_label") or result.get("label"),
        result.get("description"),
        float(result.get("confidence") or 0),
        float(result.get("probability_pd") or 0),
        float(result.get("probability_tpd") or 0),
        1 if result.get("is_valid_audio") is not False else 0,
        result.get("error_message"),
        float(audio_quality.get("duration") or 0),
        float(indicators.get("volume_score") or 0),
        float(indicators.get("intonation_score") or 0),
        float(indicators.get("pause_score") or 0),
        float(indicators.get("speech_activity_ratio") or 0),
        float(indicators.get("silence_ratio") or 0),
    )

    if DB_DRIVER == "mysql":
        return save_prediction_result_mysql(values)

    with get_connection() as connection:
        cursor = connection.execute(
            """
            INSERT INTO prediction_results (
                user_id, student_name, student_gender, predicted_label, description, confidence,
                probability_pd, probability_tpd, is_valid_audio, error_message,
                audio_duration, volume_score, intonation_score, pause_score,
                speech_activity_ratio, silence_ratio
            )
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            values,
        )
        connection.commit()
        return cursor.lastrowid


def save_prediction_result_mysql(values):
    with get_connection() as connection:
        with connection.cursor() as cursor:
            cursor.execute(
                """
                INSERT INTO prediction_results (
                    user_id, student_name, student_gender, predicted_label, description, confidence,
                    probability_pd, probability_tpd, is_valid_audio, error_message,
                    audio_duration, volume_score, intonation_score, pause_score,
                    speech_activity_ratio, silence_ratio
                )
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                """,
                values,
            )
            prediction_id = cursor.lastrowid
        connection.commit()
        return prediction_id


def get_prediction_result(prediction_id):
    init_db()
    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute(
                    """
                    SELECT
                        id, user_id, student_name, student_gender, predicted_label, description, confidence,
                        probability_pd, probability_tpd, is_valid_audio, error_message,
                        audio_duration, volume_score, intonation_score, pause_score,
                        speech_activity_ratio, silence_ratio, created_at
                    FROM prediction_results
                    WHERE id = %s
                    """,
                    (prediction_id,),
                )
                row = cursor.fetchone()
        if row:
            row["created_at"] = str(row["created_at"])
        return row

    with get_connection() as connection:
        row = connection.execute(
            """
            SELECT
                id, user_id, student_name, student_gender, predicted_label, description, confidence,
                probability_pd, probability_tpd, is_valid_audio, error_message,
                audio_duration, volume_score, intonation_score, pause_score,
                speech_activity_ratio, silence_ratio, created_at
            FROM prediction_results
            WHERE id = ?
            """,
            (prediction_id,),
        ).fetchone()

    return dict(row) if row else None


def update_prediction_result(prediction_id, values):
    init_db()
    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute(
                    """
                    UPDATE prediction_results
                    SET
                        student_name = %s,
                        student_gender = %s,
                        predicted_label = %s,
                        description = %s,
                        confidence = %s,
                        probability_pd = %s,
                        probability_tpd = %s,
                        is_valid_audio = %s,
                        error_message = %s,
                        audio_duration = %s,
                        volume_score = %s,
                        intonation_score = %s,
                        pause_score = %s,
                        speech_activity_ratio = %s,
                        silence_ratio = %s
                    WHERE id = %s
                    """,
                    (*values, prediction_id),
                )
                affected = cursor.rowcount
            connection.commit()
        return affected > 0

    with get_connection() as connection:
        cursor = connection.execute(
            """
            UPDATE prediction_results
            SET
                student_name = ?,
                student_gender = ?,
                predicted_label = ?,
                description = ?,
                confidence = ?,
                probability_pd = ?,
                probability_tpd = ?,
                is_valid_audio = ?,
                error_message = ?,
                audio_duration = ?,
                volume_score = ?,
                intonation_score = ?,
                pause_score = ?,
                speech_activity_ratio = ?,
                silence_ratio = ?
            WHERE id = ?
            """,
            (*values, prediction_id),
        )
        connection.commit()
        return cursor.rowcount > 0


def delete_prediction_result(prediction_id):
    init_db()
    if DB_DRIVER == "mysql":
        with get_connection() as connection:
            with connection.cursor() as cursor:
                cursor.execute(
                    "DELETE FROM prediction_results WHERE id = %s",
                    (prediction_id,),
                )
                affected = cursor.rowcount
            connection.commit()
        return affected > 0

    with get_connection() as connection:
        cursor = connection.execute(
            "DELETE FROM prediction_results WHERE id = ?",
            (prediction_id,),
        )
        connection.commit()
        return cursor.rowcount > 0


def list_prediction_results(limit=200, user_id=None):
    init_db()
    if DB_DRIVER == "mysql":
        return list_prediction_results_mysql(limit=limit, user_id=user_id)

    with get_connection() as connection:
        where_clause = "WHERE user_id = ?" if user_id is not None else ""
        params = (user_id, limit) if user_id is not None else (limit,)
        rows = connection.execute(
            f"""
            SELECT
                id, user_id, student_name, student_gender, predicted_label, description, confidence,
                probability_pd, probability_tpd, is_valid_audio, error_message,
                audio_duration, volume_score, intonation_score, pause_score,
                speech_activity_ratio, silence_ratio, created_at
            FROM prediction_results
            {where_clause}
            ORDER BY created_at DESC, id DESC
            LIMIT ?
            """,
            params,
        ).fetchall()

    return [dict(row) for row in rows]


def list_prediction_results_mysql(limit=200, user_id=None):
    with get_connection() as connection:
        with connection.cursor() as cursor:
            where_clause = "WHERE user_id = %s" if user_id is not None else ""
            params = (user_id, limit) if user_id is not None else (limit,)
            cursor.execute(
                f"""
                SELECT
                    id, user_id, student_name, student_gender, predicted_label, description, confidence,
                    probability_pd, probability_tpd, is_valid_audio, error_message,
                    audio_duration, volume_score, intonation_score, pause_score,
                    speech_activity_ratio, silence_ratio, created_at
                FROM prediction_results
                {where_clause}
                ORDER BY created_at DESC, id DESC
                LIMIT %s
                """,
                params,
            )
            rows = cursor.fetchall()

    for row in rows:
        row["created_at"] = str(row["created_at"])
    return rows
