import sys
import logging
from pathlib import Path

from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QAction
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel
from PySide6.QtMultimedia import (
    QMediaDevices,
    QCamera,
    QCameraDevice,
    QMediaFormat,
    QVideoFrameFormat,
    QMediaCaptureSession
)
from PySide6.QtMultimediaWidgets import QVideoWidget


# ============================================================
# LOGGER
# ============================================================

LOG_DIR = Path("logs")
LOG_DIR.mkdir(exist_ok=True)

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(LOG_DIR / "camera_debug.log", encoding="utf-8"),
        logging.StreamHandler(sys.stdout),
    ],
)

logger = logging.getLogger("camera_app")


# ============================================================
# CAMERA WINDOW
# ============================================================

class CameraWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.setWindowTitle("Camera Preview")

        # Widget wyświetlający wyłącznie obraz
        self.video_widget = QVideoWidget()
        self.setCentralWidget(self.video_widget)

        self.camera = None
        self.camera_device = None
        self.capture_session = QMediaCaptureSession()

        self.init_camera()

    # ========================================================
    # CAMERA INIT
    # ========================================================

    def init_camera(self):

        devices = QMediaDevices.videoInputs()

        logger.info("==========================================")
        logger.info("VIDEO DEVICES ENUMERATION")
        logger.info("==========================================")

        if not devices:
            logger.error("Nie znaleziono kamer.")
            return

        for idx, device in enumerate(devices):
            self.log_camera_device(device, idx)

        # wybór pierwszej kamery
        self.camera_device = devices[0]

        logger.info(f"Wybrano kamerę: {self.camera_device.description()}")

        self.camera = QCamera(self.camera_device)

        # sygnały
        self.camera.errorOccurred.connect(self.on_camera_error)
        self.camera.activeChanged.connect(self.on_active_changed)

        # video sink
        # self.camera.setVideoOutput(self.video_widget)
        self.capture_session.setCamera(self.camera)
        self.capture_session.setVideoOutput(self.video_widget)

        # przykładowe ustawienia
        self.set_camera_format(
            width=1280,
            height=720,
            fps=30,
            preferred_pixel_format=QVideoFrameFormat.PixelFormat.Format_Jpeg
        )

        self.log_runtime_camera_capabilities()

        self.camera.start()

        logger.info("Kamera uruchomiona.")

    # ========================================================
    # FORMAT SELECTION
    # ========================================================
    def set_camera_format(
        self,
        width: int,
        height: int,
        fps: int,
        preferred_pixel_format=QVideoFrameFormat.PixelFormat.Format_Jpeg,
    ):
    # def set_camera_format(self, width: int, height: int, fps: int):
        """
        Prototyp funkcji ustawiającej:
        - rozdzielczość
        - FPS

        Wybiera najbliższy pasujący format.
        """

        logger.info(
            f"Próba ustawienia formatu: {width}x{height} @ {fps} FPS"
        )

        best_match = None

        for fmt in self.camera_device.videoFormats():

            resolution = fmt.resolution()
            min_fps = fmt.minFrameRate()
            max_fps = fmt.maxFrameRate()
            pixel_format = fmt.pixelFormat()

            logger.debug(
                f"Sprawdzam format -> "
                f"{resolution.width()}x{resolution.height()} "
                f"FPS:{min_fps}-{max_fps} "
                f"PIX:{pixel_format}"
            )

            if (
                resolution.width() == width
                and resolution.height() == height
                and min_fps <= fps <= max_fps
                and pixel_format == preferred_pixel_format
            ):
                best_match = fmt
                break

        if best_match:
            self.camera.setCameraFormat(best_match)

            logger.info("Ustawiono format kamery:")
            logger.info(
                f"Resolution: "
                f"{best_match.resolution().width()}x"
                f"{best_match.resolution().height()}"
            )
            logger.info(
                f"FPS range: "
                f"{best_match.minFrameRate()} - "
                f"{best_match.maxFrameRate()}"
            )
            logger.info(
                f"Pixel format: {best_match.pixelFormat()}"
            )

        else:
            logger.warning("Nie znaleziono pasującego formatu.")

    # ========================================================
    # DEVICE LOGGER
    # ========================================================

    def log_camera_device(self, device: QCameraDevice, idx: int):

        logger.info("------------------------------------------")
        logger.info(f"KAMERA #{idx}")
        logger.info("------------------------------------------")

        logger.info(f"Description: {device.description()}")
        logger.info(f"ID: {device.id().data().decode(errors='ignore')}")

        try:
            logger.info(f"Is default: {device.isDefault()}")
        except Exception as e:
            logger.warning(f"isDefault() unsupported: {e}")

        formats = device.videoFormats()

        logger.info(f"Liczba formatów: {len(formats)}")

        for i, fmt in enumerate(formats):

            resolution = fmt.resolution()

            logger.info(f"")
            logger.info(f"FORMAT #{i}")

            logger.info(
                f"Resolution: "
                f"{resolution.width()}x{resolution.height()}"
            )

            logger.info(
                f"FPS min/max: "
                f"{fmt.minFrameRate()} / {fmt.maxFrameRate()}"
            )

            logger.info(
                f"Pixel format enum: {fmt.pixelFormat()}"
            )

            logger.info(
                f"Pixel format name: "
                f"{self.pixel_format_to_string(fmt.pixelFormat())}"
            )

    # ========================================================
    # RUNTIME CAMERA CAPABILITIES
    # ========================================================

    def log_runtime_camera_capabilities(self):

        logger.info("")
        logger.info("==========================================")
        logger.info("QCAMERA RUNTIME CAPABILITIES")
        logger.info("==========================================")

        try:
            logger.info(f"Camera active: {self.camera.isActive()}")
        except Exception as e:
            logger.warning(e)

        # Dostępne w zależności od backendu/platformy
        properties = [
            "focusMode",
            "exposureMode",
            "whiteBalanceMode",
            "flashMode",
            "torchMode",
        ]

        for prop in properties:

            try:
                value = getattr(self.camera, prop)()
                logger.info(f"{prop}: {value}")

            except Exception as e:
                logger.warning(f"{prop} unsupported: {e}")

        # backend/platform info
        logger.info("")
        logger.info("QT MULTIMEDIA INFO")

        try:
            logger.info(f"Qt version: {QApplication.qtVersion()}")
        except Exception:
            pass

        logger.info(
            "Backend zależy od platformy:"
        )
        logger.info(
            "- Windows -> MediaFoundation"
        )
        logger.info(
            "- Linux -> GStreamer / PipeWire / V4L2"
        )
        logger.info(
            "- macOS -> AVFoundation"
        )

    # ========================================================
    # PIXEL FORMAT NAME
    # ========================================================

    def pixel_format_to_string(self, pixel_format):

        mapping = {
            QVideoFrameFormat.PixelFormat.Format_YUV420P: "YUV420P",
            QVideoFrameFormat.PixelFormat.Format_NV12: "NV12",
            QVideoFrameFormat.PixelFormat.Format_NV21: "NV21",
            QVideoFrameFormat.PixelFormat.Format_UYVY: "UYVY",
            QVideoFrameFormat.PixelFormat.Format_YUYV: "YUYV",
            QVideoFrameFormat.PixelFormat.Format_Jpeg: "MJPG/JPEG",
        }

        return mapping.get(pixel_format, str(pixel_format))

    # ========================================================
    # SIGNALS
    # ========================================================

    def on_camera_error(self, error, error_string):
        logger.error(f"CAMERA ERROR: {error}")
        logger.error(f"ERROR STRING: {error_string}")

    def on_active_changed(self, active):
        logger.info(f"Camera active changed: {active}")

    # ========================================================
    # CLEANUP
    # ========================================================

    # def closeEvent(self, event):

    #     logger.info("Zamykanie aplikacji.")

    #     if self.camera:
    #         self.camera.stop()

    #     super().closeEvent(event)

    def closeEvent(self, event):

        logger.info("Zamykanie aplikacji.")

        try:
            if self.camera:
                logger.info("Stopping camera...")
                self.camera.stop()

            if self.capture_session:
                logger.info("Detaching camera from capture session...")
                self.capture_session.setCamera(None)
                self.capture_session.setVideoOutput(None)

        except Exception as e:
            logger.exception(e)

        super().closeEvent(event)

# ============================================================
# MAIN
# ============================================================

def main():

    app = QApplication(sys.argv)

    window = CameraWindow()

    # tylko obraz
    window.resize(1280, 720)

    # fullscreen:
    # window.showFullScreen()

    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()