package com.mrousavy.camera.core

import android.media.MediaCodec
import android.media.MediaCodec.BufferInfo
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.util.Log
import android.util.Size
import android.view.Surface
import com.mrousavy.camera.types.Orientation
import com.mrousavy.camera.types.RecordVideoOptions
import java.io.File

/**
 * A recording manager that produces HLS-compatible fragmented MP4 segments.
 *
 * Uses HlsMuxer (following Android's MediaMuxer pattern) to produce:
 * - init.mp4: Initialization segment (ftyp + moov with mvex)
 * - 0.mp4, 1.mp4, ...: Media segments (moof + mdat)
 */
class FragmentedRecordingManager(
    private val encoder: MediaCodec,
    private val muxer: HlsMuxer,
    private val configuredFps: Int
) : MediaCodec.Callback(), ChunkedRecorderInterface {

    companion object {
        private const val TAG = "FragmentedRecorder"
        private const val DEFAULT_SEGMENT_DURATION_SECONDS = 6

        fun fromParams(
            callbacks: CameraSession.Callback,
            size: Size,
            enableAudio: Boolean,
            fps: Int? = null,
            cameraOrientation: Orientation,
            bitRate: Int,
            options: RecordVideoOptions,
            outputDirectory: File,
            segmentDurationSeconds: Int = DEFAULT_SEGMENT_DURATION_SECONDS
        ): FragmentedRecordingManager {
            val mimeType = options.videoCodec.toMimeType()
            // Use cameraOrientation from Android (computed from device rotation)
            // instead of options.orientation from JS which may be stale
            val recordingOrientationDegrees = cameraOrientation.toDegrees()

            // Swap dimensions based on orientation - same logic as ChunkedRecordingManager
            // When camera is in landscape orientation, we need to swap width/height for the encoder
            val (width, height) = if (cameraOrientation.isLandscape()) {
                size.height to size.width
            } else {
                size.width to size.height
            }

            Log.d(TAG, "Input size: ${size.width}x${size.height}, " +
                    "encoder size: ${width}x${height}, " +
                    "orientation: $cameraOrientation ($recordingOrientationDegrees°)")

            val format = MediaFormat.createVideoFormat(mimeType, width, height)
            val codec = MediaCodec.createEncoderByType(mimeType)

            format.setInteger(
                MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
            )

            val effectiveFps = fps ?: 30
            format.setInteger(MediaFormat.KEY_FRAME_RATE, effectiveFps)
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, segmentDurationSeconds)
            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)

            Log.d(TAG, "Video Format: $format, orientation: $recordingOrientationDegrees")

            codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

            // Create muxer with callbacks and orientation
            val muxer = HlsMuxer(
                outputDirectory = outputDirectory,
                callback = object : HlsMuxer.Callback {
                    override fun onInitSegmentReady(file: File) {
                        callbacks.onInitSegmentReady(file)
                    }

                    override fun onMediaSegmentReady(file: File, index: Int, durationUs: Long) {
                        callbacks.onVideoChunkReady(file, index, durationUs)
                    }
                },
                orientationDegrees = recordingOrientationDegrees
            )
            muxer.setSegmentDuration(segmentDurationSeconds * 1_000_000L)

            Log.d(TAG, "Created HlsMuxer with orientation: $recordingOrientationDegrees degrees, fps: $effectiveFps")

            return FragmentedRecordingManager(codec, muxer, effectiveFps)
        }
    }

    private var recording = false
    private var muxerStarted = false
    private var trackIndex = -1

    override val surface: Surface = encoder.createInputSurface()

    init {
        encoder.setCallback(this)
    }

    override fun start() {
        encoder.start()
        recording = true
    }

    override fun finish() {
        synchronized(this) {
            recording = false

            if (muxerStarted) {
                muxer.stop()
                muxer.release()
            }

            try {
                encoder.stop()
                encoder.release()
            } catch (e: Exception) {
                Log.e(TAG, "Error stopping encoder", e)
            }
        }
    }

    // MediaCodec.Callback methods

    override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
        // Not used for Surface input
    }

    override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, bufferInfo: BufferInfo) {
        synchronized(this) {
            if (!recording) {
                encoder.releaseOutputBuffer(index, false)
                return
            }

            if (!muxerStarted) {
                encoder.releaseOutputBuffer(index, false)
                return
            }

            val buffer = encoder.getOutputBuffer(index)
            if (buffer == null) {
                Log.e(TAG, "getOutputBuffer returned null")
                encoder.releaseOutputBuffer(index, false)
                return
            }

            try {
                muxer.writeSampleData(trackIndex, buffer, bufferInfo)
            } catch (e: Exception) {
                Log.e(TAG, "Error writing sample", e)
            }

            encoder.releaseOutputBuffer(index, false)
        }
    }

    override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
        Log.e(TAG, "Codec error: ${e.message}")
    }

    override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
        synchronized(this) {
            Log.i(TAG, "Output format changed: $format")

            // Pass configured fps to muxer (not the encoder's output format fps which may differ)
            trackIndex = muxer.addTrack(format, configuredFps)
            muxer.start()
            muxerStarted = true
        }
    }
}
