package com.example.livingai.data.repository.media

import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.Rect
import android.provider.MediaStore
import androidx.camera.core.ImageProxy
import com.example.livingai.data.ml.DistanceEstimatorImpl
import com.example.livingai.data.ml.MidasDepthEstimator
import com.example.livingai.domain.ml.AIModel
import com.example.livingai.domain.ml.FrameMetadataProvider
import com.example.livingai.domain.ml.FrameMetadataProvider.toFrameData
import com.example.livingai.domain.ml.Orientation
import com.example.livingai.domain.ml.OrientationState
import com.example.livingai.domain.repository.CameraRepository
import com.example.livingai.utils.TiltSensorManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class CameraRepositoryImpl(
    private val aiModel: AIModel,
    private val tiltSensorManager: TiltSensorManager,
    private val context: Context
) : CameraRepository {

    private val distanceEstimator = DistanceEstimatorImpl()
    private val midasEstimator = MidasDepthEstimator(context)

    init {
        FrameMetadataProvider.aiModel = aiModel
        FrameMetadataProvider.tiltSensorManager = tiltSensorManager
    }

    override suspend fun captureImage(imageProxy: ImageProxy): Bitmap =
        withContext(Dispatchers.IO) {
            val rotation = imageProxy.imageInfo.rotationDegrees
            val bitmap = imageProxy.toBitmap()
            imageProxy.close()

            if (rotation != 0) {
                val matrix = Matrix().apply { postRotate(rotation.toFloat()) }
                Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
            } else bitmap
        }

    override suspend fun processFrame(
        bitmap: Bitmap,
        requestedOrientation: Orientation,
        silhouetteBitmap: Bitmap,
        realObjectHeightMeters: Float?,
        focalLengthPixels: Float,
        boundingBox: Rect?
    ): OrientationState = withContext(Dispatchers.Default) {

        // 1. Collect segmentation
        // Use the passed boundingBox if available, otherwise it relies on FrameMetadataProvider running segmentation again
        // But FrameMetadataProvider.collectMetadata runs segmentation internally.
        // To avoid re-running detection/segmentation if we already have bbox, we can pass it.
        // However, FrameMetadataProvider currently calls getSegmentation(bitmap) which calls aiModel.segmentImage(bitmap).
        // AIModel.segmentImage is returning null in current impl.
        
        // ISSUE: processFrame relies on FrameMetadataProvider.collectMetadata -> getSegmentation -> aiModel.segmentImage
        // But AIModelImpl.segmentImage returns null!
        // So bbox will be null, and processFrame returns early with "Segmentation missing".
        
        // FIX: We need to use the detection result we already have from CameraViewModel.
        // We will mock the segmentation result using the bounding box from object detection.
        // And for the mask, since we don't have segmentation, we can either:
        // a) Create a dummy mask filled within the bbox (simple box mask)
        // b) Or just proceed if DistanceEstimator can handle it (it needs mask).
        
        // Let's create a synthetic mask from the bbox.
        val syntheticMeta = if (boundingBox != null) {
            // Create a simple mask where pixels inside bbox are true
            // This is computationally expensive to do full bitmap, so be careful.
            // But we need a Bitmap mask for DistanceEstimator.
            // Let's create a black bitmap with white rect.
            
            // NOTE: This runs on Default dispatcher, so should be okay-ish.
            
            // However, FrameMetadataProvider.collectMetadata does more (IMU, Depth).
            // Let's manually construct metadata.
            
            val maskBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
            val canvas = android.graphics.Canvas(maskBitmap)
            val paint = android.graphics.Paint().apply { color = android.graphics.Color.WHITE }
            canvas.drawRect(boundingBox, paint)
            
            val imu = FrameMetadataProvider.getIMU()
            val rot = FrameMetadataProvider.getRotation()
            val depth = FrameMetadataProvider.getDepthData()
            
            FrameMetadataProvider.FrameCollectedMetadata(
                segmentationMaskBitmap = maskBitmap,
                segmentationBox = boundingBox,
                depthMeters = depth.depthMeters,
                depthWidth = depth.width,
                depthHeight = depth.height,
                depthConfidence = depth.confidence,
                pitch = imu.pitch,
                roll = imu.roll,
                yaw = imu.yaw,
                rotationDegrees = rot
            )
        } else {
             FrameMetadataProvider.collectMetadata(bitmap)
        }

        val bbox = syntheticMeta.segmentationBox
        // val mask = syntheticMeta.segmentationMaskBitmap // Mask is used inside distanceEstimator

        if (bbox == null) {
            return@withContext OrientationState(
                success = false,
                reason = "Segmentation missing",
                pixelMetrics = null,
                orientationMatched = false,
                iouScore = null,
                relativeDepth = null,
                absoluteDistanceMeters = null
            )
        }

        // 2. MiDaS (relative + absolute if reference height provided)
        val midasResult = midasEstimator.analyzeObject(
            bitmap = bitmap,
            bbox = bbox,
            realObjectHeightMeters = realObjectHeightMeters,
            focalLengthPixels = focalLengthPixels
        )

        // 3. Build FrameData with relative depth only
        val frameData = syntheticMeta.toFrameData(bitmap).copy(
            medianDepth = midasResult?.relativeDepth
        )

        // 4. Orientation detection
        val orientationState = distanceEstimator.processFrame(
            frameData = frameData,
            requestedOrientation = requestedOrientation,
            silhouetteBitmap = silhouetteBitmap
        )

        // 5. Inject relative + absolute values into final result
        orientationState.copy(
            relativeDepth = midasResult?.relativeDepth,
            absoluteDistanceMeters = midasResult?.absoluteDistanceMeters
        )
    }

    override suspend fun saveImage(
        bitmap: Bitmap,
        animalId: String,
        orientation: String?
    ): String = withContext(Dispatchers.IO) {

        val suffix = orientation?.let { "_$it" } ?: ""
        val fileName = "$animalId$suffix.jpg"

        val values = ContentValues().apply {
            put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
            put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/LivingAI/Media/$animalId")
                put(MediaStore.Images.Media.IS_PENDING, 1)
            }
        }

        val resolver = context.contentResolver
        val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            ?: throw RuntimeException("Failed to insert image")

        try {
            resolver.openOutputStream(uri)?.use { out ->
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
            }

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                values.clear()
                values.put(MediaStore.Images.Media.IS_PENDING, 0)
                resolver.update(uri, values, null, null)
            }

        } catch (e: Exception) {
            resolver.delete(uri, null, null)
            throw e
        }

        uri.toString()
    }
}
