import psycopg2
import json

DB_CONFIG = {
    "host": "192.168.1.160",
    "port": 5433,
    "dbname": "dv",
    "user": "dev",
    "password": "dev"
}

def get_db_connection():
    return psycopg2.connect(**DB_CONFIG)

def migrate_schema():
    """Add new columns to person table if they don't exist. Safe to call repeatedly."""
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        for sql in [
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS prompt TEXT",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS pose TEXT",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS sort_order INTEGER",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS group_name TEXT",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS hidden BOOLEAN DEFAULT FALSE",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS has_background BOOLEAN DEFAULT TRUE",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS source_refs TEXT",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS has_clothing BOOLEAN DEFAULT NULL",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS content_type TEXT DEFAULT 'image'",
            "ALTER TABLE person ADD COLUMN IF NOT EXISTS faceswap_source_video TEXT",
        ]:
            cur.execute(sql)
        conn.commit()
    finally:
        cur.close()
        conn.close()

def upsert_person(filename, filepath=None, name=None, group_id=None, tags=None,
                  embedding=None, clip_description=None, prompt=None, pose=None,
                  sort_order=None, group_name=None, hidden=None,
                  has_background=None, source_refs=None, has_clothing=None,
                  content_type=None, faceswap_source_video=None):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("""
            INSERT INTO person (filename, filepath, name, group_id, tags, embedding,
                                clip_description, prompt, pose, sort_order, group_name, hidden,
                                has_background, source_refs, has_clothing,
                                content_type, faceswap_source_video)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            ON CONFLICT (filename) DO UPDATE
            SET filepath              = COALESCE(EXCLUDED.filepath,              person.filepath),
                name                  = COALESCE(EXCLUDED.name,                  person.name),
                group_id              = COALESCE(EXCLUDED.group_id,              person.group_id),
                tags                  = COALESCE(EXCLUDED.tags,                  person.tags),
                embedding             = COALESCE(EXCLUDED.embedding,             person.embedding),
                clip_description      = COALESCE(EXCLUDED.clip_description,      person.clip_description),
                prompt                = COALESCE(EXCLUDED.prompt,                person.prompt),
                pose                  = COALESCE(EXCLUDED.pose,                  person.pose),
                sort_order            = COALESCE(EXCLUDED.sort_order,            person.sort_order),
                group_name            = COALESCE(EXCLUDED.group_name,            person.group_name),
                hidden                = COALESCE(EXCLUDED.hidden,                person.hidden),
                has_background        = COALESCE(EXCLUDED.has_background,        person.has_background),
                source_refs           = COALESCE(EXCLUDED.source_refs,           person.source_refs),
                has_clothing          = COALESCE(EXCLUDED.has_clothing,          person.has_clothing),
                content_type          = COALESCE(EXCLUDED.content_type,          person.content_type),
                faceswap_source_video = COALESCE(EXCLUDED.faceswap_source_video, person.faceswap_source_video);
        """, (filename, filepath, name, group_id,
              json.dumps(tags) if tags else None,
              embedding, clip_description, prompt, pose, sort_order, group_name, hidden,
              has_background, source_refs, has_clothing,
              content_type, faceswap_source_video))
        conn.commit()
    finally:
        cur.close()
        conn.close()

def set_hidden(filename, hidden: bool):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("UPDATE person SET hidden = %s WHERE filename = %s", (hidden, filename))
        conn.commit()
    finally:
        cur.close()
        conn.close()

def get_person(filename):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("""
            SELECT name, group_id, tags, embedding, clip_description, filepath,
                   prompt, pose, sort_order, group_name, hidden, has_background, source_refs,
                   has_clothing
            FROM person WHERE filename = %s
        """, (filename,))
        return cur.fetchone()
    finally:
        cur.close()
        conn.close()

def list_persons():
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("""
            SELECT filename, name, group_id, clip_description,
                   prompt, pose, sort_order, group_name, hidden, has_background, source_refs,
                   has_clothing, content_type, faceswap_source_video
            FROM person
        """)
        return cur.fetchall()
    finally:
        cur.close()
        conn.close()

def search_similar(embedding, limit=10):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        embedding_str = "[" + ",".join(map(str, embedding)) + "]"
        cur.execute("""
            SELECT filename, name, group_id, clip_description, embedding <=> %s AS distance
            FROM person
            WHERE embedding IS NOT NULL
            ORDER BY distance ASC
            LIMIT %s;
        """, (embedding_str, limit))
        return cur.fetchall()
    finally:
        cur.close()
        conn.close()

def delete_person(filename):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("DELETE FROM person WHERE filename = %s", (filename,))
        conn.commit()
    finally:
        cur.close()
        conn.close()

def delete_group(group_id):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("DELETE FROM person WHERE group_id = %s", (group_id,))
        conn.commit()
    finally:
        cur.close()
        conn.close()

def get_group_files(group_id):
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("SELECT filename, filepath FROM person WHERE group_id = %s", (group_id,))
        return cur.fetchall()
    finally:
        cur.close()
        conn.close()

def set_group_order(group_id, ordered_filenames):
    """Assign sort_order 0,1,2,... to filenames in the given order."""
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        for idx, fname in enumerate(ordered_filenames):
            cur.execute(
                "UPDATE person SET sort_order = %s WHERE filename = %s",
                (idx, fname)
            )
        conn.commit()
    finally:
        cur.close()
        conn.close()

def get_group_order(group_id):
    """Return [(filename, sort_order), ...] sorted by sort_order NULLS LAST."""
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("""
            SELECT filename, sort_order
            FROM person
            WHERE group_id = %s
            ORDER BY sort_order NULLS LAST, filename
        """, (group_id,))
        return cur.fetchall()
    finally:
        cur.close()
        conn.close()

def set_group_name(group_id, name):
    """Set group_name for every file in the group."""
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("UPDATE person SET group_name = %s WHERE group_id = %s", (name, group_id))
        conn.commit()
    finally:
        cur.close()
        conn.close()

def get_next_sort_order(group_id):
    """Return max(sort_order)+1 for the group, or 1 if no sorted members exist."""
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute(
            "SELECT COALESCE(MAX(sort_order), 0) + 1 FROM person WHERE group_id = %s",
            (group_id,)
        )
        return cur.fetchone()[0]
    finally:
        cur.close()
        conn.close()


def get_all_group_names():
    """Return {group_id: group_name} for groups that have a name set."""
    conn = get_db_connection()
    cur = conn.cursor()
    try:
        cur.execute("""
            SELECT DISTINCT ON (group_id) group_id, group_name
            FROM person
            WHERE group_id IS NOT NULL AND group_name IS NOT NULL
        """)
        return {row[0]: row[1] for row in cur.fetchall()}
    finally:
        cur.close()
        conn.close()
