import os
import sys
import sqlite3
import hashlib
import pandas as pd

def get_db_path():
    """
    Returns the absolute path to the SQLite database.
    If the application is running inside a Docker container, it uses /app/data/nbu_insights.db.
    If running locally, it defaults to a local './data' directory.
    """
    container_data_dir = "/app/data"
    if os.path.exists(container_data_dir) or os.environ.get("NBU_INSIGHTS_RUNNING") == "1":
        db_dir = container_data_dir
    else:
        base_dir = os.path.dirname(os.path.abspath(__file__))
        db_dir = os.path.join(base_dir, "data")
        
    os.makedirs(db_dir, exist_ok=True)
    return os.path.join(db_dir, "nbu_insights.db")

def get_connection():
    """
    Establishes and returns a connection to the SQLite database.
    Rows are configured to be accessible by column names like a dictionary.
    """
    db_path = get_db_path()
    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    return conn

def hash_password(password, salt=None):
    """
    Hashes a password using PBKDF2-SHA256 with 100,000 iterations and a unique salt.
    Returns string 'salt_hex:hash_hex' which is safe to store in the DB.
    """
    if salt is None:
        salt = os.urandom(16)
    elif isinstance(salt, str):
        salt = bytes.fromhex(salt)
        
    key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
    return salt.hex() + ":" + key.hex()

def verify_password(stored_password_hash, provided_password):
    """
    Verifies a password against its stored PBKDF2-SHA256 hash.
    """
    try:
        salt_hex, key_hex = stored_password_hash.split(":")
        salt = bytes.fromhex(salt_hex)
        expected_key_hex = hashlib.pbkdf2_hmac('sha256', provided_password.encode('utf-8'), salt, 100000).hex()
        return key_hex == expected_key_hex
    except Exception:
        return False

def init_db():
    """
    Initializes the SQLite database tables according to v2.0/v3.0 specs.
    """
    conn = get_connection()
    cursor = conn.cursor()
    
    # Processed files table
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS processed_files (
        file_hash TEXT PRIMARY KEY,
        file_name TEXT,
        upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    """)
    
    # Backup jobs table
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS backup_jobs (
        job_id INTEGER PRIMARY KEY,
        client TEXT,
        policy TEXT,
        type TEXT,
        exit_code INTEGER,
        start_time TIMESTAMP,
        finish_time TIMESTAMP,
        duration_secs INTEGER,
        mbytes REAL,
        files_count INTEGER,
        primary_server TEXT,
        media_server TEXT,
        is_rerun_success INTEGER DEFAULT 0
    );
    """)
    
    # Technician actions table
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS job_actions (
        job_id INTEGER PRIMARY KEY,
        action_taken TEXT,
        status TEXT DEFAULT 'Pendente',
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY(job_id) REFERENCES backup_jobs(job_id)
    );
    """)
    
    # Users table
    cursor.execute("""
    CREATE TABLE IF NOT EXISTS users (
        username TEXT PRIMARY KEY,
        password_hash TEXT,
        role TEXT DEFAULT 'user' -- 'admin' or 'user'
    );
    """)
    
    conn.commit()
    conn.close()

def is_file_processed(file_hash):
    """
    Checks if a CSV file (based on its unique MD5 hash) has already been processed.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT 1 FROM processed_files WHERE file_hash = ?", (file_hash,))
    row = cursor.fetchone()
    conn.close()
    return row is not None

def mark_file_processed(file_hash, file_name):
    """
    Logs a file as processed.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute(
        "INSERT OR IGNORE INTO processed_files (file_hash, file_name) VALUES (?, ?)",
        (file_hash, file_name)
    )
    conn.commit()
    conn.close()

def save_jobs(jobs_df):
    """
    Saves or updates jobs into the backup_jobs table using SQL UPSERT.
    """
    conn = get_connection()
    cursor = conn.cursor()
    
    for _, row in jobs_df.iterrows():
        start_time = str(row['Start Time']) if not pd.isna(row['Start Time']) else None
        finish_time = str(row['Finish Time']) if not pd.isna(row['Finish Time']) else None
        
        cursor.execute("""
            INSERT INTO backup_jobs (
                job_id, client, policy, type, exit_code, start_time, finish_time, 
                duration_secs, mbytes, files_count, primary_server, media_server, is_rerun_success
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(job_id) DO UPDATE SET
                client = excluded.client,
                policy = excluded.policy,
                type = excluded.type,
                exit_code = excluded.exit_code,
                start_time = excluded.start_time,
                finish_time = excluded.finish_time,
                duration_secs = excluded.duration_secs,
                mbytes = excluded.mbytes,
                files_count = excluded.files_count,
                primary_server = excluded.primary_server,
                media_server = excluded.media_server,
                is_rerun_success = excluded.is_rerun_success;
        """, (
            int(row['Job ID']),
            row['Client'],
            row['Policy'],
            row['Type'],
            int(row['Exit Code']),
            start_time,
            finish_time,
            int(row['Duration_Sec']),
            float(row['MBytes']),
            int(row['# of Files']),
            row['Primary Server'],
            row['Media Server'],
            int(row['is_rerun_success'])
        ))
        
    conn.commit()
    conn.close()

def save_action(job_id, action_taken, status):
    """
    Saves or updates a mitigation action for a failed backup job.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("""
        INSERT INTO job_actions (job_id, action_taken, status, updated_at)
        VALUES (?, ?, ?, CURRENT_TIMESTAMP)
        ON CONFLICT(job_id) DO UPDATE SET
            action_taken = excluded.action_taken,
            status = excluded.status,
            updated_at = CURRENT_TIMESTAMP;
    """, (job_id, action_taken, status))
    conn.commit()
    conn.close()

def get_historical_jobs():
    """
    Retrieves all records from the backup_jobs table joined with job_actions.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("""
        SELECT j.*, a.action_taken, COALESCE(a.status, 'Pendente') as status, a.updated_at
        FROM backup_jobs j
        LEFT JOIN job_actions a ON j.job_id = a.job_id
    """)
    rows = cursor.fetchall()
    conn.close()
    return [dict(row) for row in rows]

# --- User Management CRUD Methods ---

def create_user(username, password, role='user'):
    """
    Hashes the password and creates a new user inside the SQLite store.
    Username is stored in lowercase to ensure uniqueness.
    """
    conn = get_connection()
    cursor = conn.cursor()
    password_hash = hash_password(password)
    try:
        cursor.execute(
            "INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)",
            (username.lower().strip(), password_hash, role)
        )
        conn.commit()
        success = True
    except sqlite3.IntegrityError:
        # Username collision
        success = False
    conn.close()
    return success

def get_user(username):
    """
    Retrieves user profile dictionary by username.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE username = ?", (username.lower().strip(),))
    row = cursor.fetchone()
    conn.close()
    return dict(row) if row else None

def has_admin_user():
    """
    Returns True if there is at least one administrator user registered.
    Used for routing to the initial bootstrap setup screen.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT 1 FROM users WHERE role = 'admin' LIMIT 1")
    row = cursor.fetchone()
    conn.close()
    return row is not None

def get_all_users():
    """
    Returns lists of all user details (excluding their hashed passwords).
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("SELECT username, role FROM users ORDER BY username")
    rows = cursor.fetchall()
    conn.close()
    return [dict(row) for row in rows]

def delete_user(username):
    """
    Removes a user by their username.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("DELETE FROM users WHERE username = ?", (username.lower().strip(),))
    conn.commit()
    conn.close()

def reset_all_data():
    """
    Destructive helper: Truncates all backup jobs, processed file hashes, 
    and technician actions. User accounts are NOT deleted.
    """
    conn = get_connection()
    cursor = conn.cursor()
    cursor.execute("DELETE FROM backup_jobs")
    cursor.execute("DELETE FROM job_actions")
    cursor.execute("DELETE FROM processed_files")
    conn.commit()
    conn.close()
