import os
import queue
import logging
import threading

from flask import Flask
from dotenv import load_dotenv

from app.admin import admin_blueprint
from app.admin.routes import auth
from app.routes import main_blueprint
from app.subadmin import subadmin_blueprint
from models.user_model import initialize_database
from config.db_config import get_db_connection


# ─────────────────────────────────────────────────────────────────────────────
# Global queue — shared between upload route (producer) and worker (consumer)
# Each item: {"processed_s3_key": str, "event_image_id": int}
# ─────────────────────────────────────────────────────────────────────────────
match_queue: queue.Queue = queue.Queue()


def _match_worker():
    """
    Single background thread started once when Flask app starts.
    Picks jobs from match_queue one by one and runs face matching.
    Photographer can keep uploading — jobs just join the queue end.
    This thread NEVER stops and NEVER affects the main app.
    """
    logger = logging.getLogger("match_worker")
    logger.info("[queue-worker] Started. Waiting for jobs...")

    while True:
        try:
            job              = match_queue.get(block=True)   # wait until job arrives
            processed_s3_key = job["processed_s3_key"]
            event_image_id   = job["event_image_id"]

            logger.info(
                f"[queue-worker] Processing image_id={event_image_id} "
                f"| queue_remaining={match_queue.qsize()}"
            )

            _run_face_match(processed_s3_key, event_image_id)

        except Exception as exc:
            logger.error(f"[queue-worker] Unexpected error: {exc}")
        finally:
            try:
                match_queue.task_done()
            except Exception:
                pass


def _run_face_match(processed_s3_key: str, event_image_id: int) -> None:
    """
    Face-match logic for one image.
    Uses processed image → detect ALL faces → match each against Rekognition
    → store event_image_id in images_array for EVERY matched user.
    Group photo: same image_id stored for every person found in the photo.
    Name + email come from users table via JOIN — not stored separately.
    """
    logger = logging.getLogger("match_worker")

    SIMILARITY_THRESHOLD = 95.0
    FACE_PADDING_RATIO   = 0.2
    MIN_FACE_SIZE_PIXELS = 40

    try:
        from io import BytesIO
        from PIL import Image, UnidentifiedImageError
        from app.aws_services import AWSService
        from models.user_model import get_user_by_face_id, append_image_to_user_array

        aws = AWSService()

        # ── 1. Download processed image ───────────────────────────────────
        try:
            processed_bytes = aws.download_file_bytes(processed_s3_key)
        except Exception as exc:
            logger.error(f"[queue-worker] Download failed {processed_s3_key}: {exc}")
            return

        # ── 2. Detect ALL faces ───────────────────────────────────────────
        try:
            face_details = aws.detect_faces(processed_s3_key)
        except Exception as exc:
            logger.error(f"[queue-worker] detect_faces failed image_id={event_image_id}: {exc}")
            return

        if not face_details:
            logger.info(f"[queue-worker] No faces in image_id={event_image_id}")
            return

        logger.info(
            f"[queue-worker] {len(face_details)} face(s) detected "
            f"in image_id={event_image_id}"
        )

        # ── 3. Open image for face cropping ──────────────────────────────
        try:
            pil_image = Image.open(BytesIO(processed_bytes)).convert("RGB")
        except (UnidentifiedImageError, OSError) as exc:
            logger.error(f"[queue-worker] Cannot open image_id={event_image_id}: {exc}")
            return

        img_w, img_h     = pil_image.size
        matched_user_ids = set()   # avoid storing same user twice per image

        # ── 4. Match each detected face ───────────────────────────────────
        for idx, face_detail in enumerate(face_details, start=1):
            bb     = face_detail.get("BoundingBox", {})
            left   = int(bb.get("Left",   0) * img_w)
            top    = int(bb.get("Top",    0) * img_h)
            width  = int(bb.get("Width",  0) * img_w)
            height = int(bb.get("Height", 0) * img_h)

            if width < MIN_FACE_SIZE_PIXELS or height < MIN_FACE_SIZE_PIXELS:
                logger.info(f"[queue-worker] Face {idx} too small, skip")
                continue

            pad_x = int(width  * FACE_PADDING_RATIO)
            pad_y = int(height * FACE_PADDING_RATIO)

            crop = pil_image.crop((
                max(0, left - pad_x),
                max(0, top  - pad_y),
                min(img_w, left + width  + pad_x),
                min(img_h, top  + height + pad_y),
            ))

            buf = BytesIO()
            crop.save(buf, format="JPEG", quality=90)
            face_bytes = buf.getvalue()

            try:
                matches = aws.search_face_by_bytes(
                    face_bytes,
                    face_match_threshold=SIMILARITY_THRESHOLD,
                    max_faces=1,
                )
            except Exception as exc:
                logger.error(f"[queue-worker] search failed face {idx}: {exc}")
                continue

            if not matches:
                logger.info(f"[queue-worker] Face {idx}: no match")
                continue

            best       = matches[0]
            similarity = float(best["Similarity"])
            face_id    = best["Face"]["FaceId"]

            # Get user from users table using face_id
            user = get_user_by_face_id(face_id)
            if not user:
                logger.info(f"[queue-worker] Face {idx}: FaceId {face_id} not in users table")
                continue

            user_id = user["id"]

            # Skip if same user already matched from another face in this image
            if user_id in matched_user_ids:
                continue
            matched_user_ids.add(user_id)

            # ── 5. Store image_id in images_array ─────────────────────────
            try:
                append_image_to_user_array(user_id, event_image_id)
                logger.info(
                    f"[queue-worker] MATCH image_id={event_image_id} -> "
                    f"user_id={user_id} name={user.get('name')} "
                    f"similarity={similarity:.1f}%"
                )
            except Exception as exc:
                logger.error(
                    f"[queue-worker] append failed user_id={user_id}: {exc}"
                )

        logger.info(
            f"[queue-worker] Done image_id={event_image_id} "
            f"-> matched {len(matched_user_ids)} user(s)"
        )

    except Exception as exc:
        logger.error(f"[queue-worker] Fatal error image_id={event_image_id}: {exc}")


def create_app():
    load_dotenv()

    logging.basicConfig(
        level=os.getenv("LOG_LEVEL", "INFO").upper(),
        format="%(asctime)s %(levelname)s %(name)s: %(message)s",
    )

    app = Flask(__name__)

    app.config["MAX_CONTENT_LENGTH"] = 25 * 1024 * 1024
    app.config["SECRET_KEY"] = os.getenv(
        "FLASK_SECRET_KEY",
        "event-photo-delivery-admin-secret"
    )

    initialize_database()

    # ── Start single background worker (runs forever, daemon so it dies with app) ──
    worker = threading.Thread(target=_match_worker, daemon=True, name="match-worker")
    worker.start()
    logging.getLogger(__name__).info("[queue-worker] Background worker thread started.")

    # ── Register all blueprints ───────────────────────────────────────────
    app.register_blueprint(auth)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(admin_blueprint)
    app.register_blueprint(subadmin_blueprint)

    @app.context_processor
    def inject_sidebar_data():
        try:
            from config.db_config import get_db_connection
            db = get_db_connection()
            if db is None:
                return {
                    "event_name": "",
                    "logo_path": ""
                }
            cursor = db.cursor(dictionary=True)
            cursor.execute("""
                SELECT event_name FROM events ORDER BY id DESC LIMIT 1
            """)
            event = cursor.fetchone()

            cursor.execute("""
                SELECT image_path FROM admin_assets WHERE type='logo' ORDER BY id DESC LIMIT 1
            """)
            logo = cursor.fetchone()
            cursor.close()
            db.close()

            return {
                "event_name": event["event_name"] if event is not None else "",
                "logo_path": "/static/" + logo["image_path"] if logo is not None else ""
            }

        except Exception as e:
            print("Sidebar error:", e)
            return {
                "event_name": "",
                "logo_path": ""
            }

    return app