package com.horrortv.app.data.repository

import android.util.Log
import android.util.LruCache
import com.horrortv.app.data.remote.omdb.OmdbApiService
import com.horrortv.app.domain.model.AppError
import com.horrortv.app.domain.model.Movie
import com.horrortv.app.domain.model.MovieCategory
import com.horrortv.app.domain.repository.MovieRepository
import com.horrortv.app.domain.repository.Result
import com.horrortv.app.util.ApiException
import com.horrortv.app.util.Constants
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class MovieRepositoryImpl @Inject constructor(
    private val apiService: OmdbApiService
) : MovieRepository {
    
    companion object {
        private const val TAG = "MovieRepositoryImpl"
    }
    
    private data class CacheEntry<T>(
        val data: T,
        val timestamp: Long,
        val ttlMs: Long
    )
    
    private val categoryCache = LruCache<String, CacheEntry<List<Movie>>>(20)
    private val searchCache = LruCache<String, CacheEntry<List<Movie>>>(50)
    private val detailCache = LruCache<String, CacheEntry<Movie>>(100)
    
    private fun categoryTtlMs(): Long = Constants.Cache.CATEGORY_CACHE_HOURS * 60 * 60 * 1000L
    private fun searchTtlMs(): Long = Constants.Cache.SEARCH_CACHE_HOURS * 60 * 60 * 1000L
    private fun detailTtlMs(): Long = Constants.Cache.DETAIL_CACHE_HOURS * 60 * 60 * 1000L
    
    private fun buildCategoryKey(category: String): String = "cat:${category.lowercase()}"
    private fun buildSearchKey(query: String): String = "search:${query.trim().lowercase()}"
    private fun buildDetailKey(imdbId: String): String = "detail:${imdbId.lowercase()}"
    
    private fun <T> getFromCache(cache: LruCache<String, CacheEntry<T>>, key: String): T? {
        val entry = cache.get(key) ?: return null
        if (System.currentTimeMillis() - entry.timestamp > entry.ttlMs) {
            cache.remove(key)
            return null
        }
        return entry.data
    }
    
    private fun <T> putInCache(cache: LruCache<String, CacheEntry<T>>, key: String, data: T, ttlMs: Long) {
        cache.put(key, CacheEntry(data, System.currentTimeMillis(), ttlMs))
    }
    
    override suspend fun getFeaturedCategories(): List<MovieCategory> = withContext(Dispatchers.Default) {
        Log.d(TAG, "Fetching featured categories with limited concurrency")
        
        coroutineScope {
            Constants.HORROR_CATEGORIES
                .chunked(3)
                .map { batch ->
                    batch.map { category ->
                        async {
                            val cacheKey = buildCategoryKey(category)
                            val cached = getFromCache(categoryCache, cacheKey)
                            if (cached != null) {
                                Log.d(TAG, "Cache HIT for category: $category")
                                MovieCategory(category, cached)
                            } else {
                                try {
                                    Log.d(TAG, "Fetching category: $category")
                                    val response = apiService.searchByCategory(category, page = 1)
                                    if (response.response == "False") {
                                        Log.w(TAG, "API error for category: $category - ${response.error}")
                                        MovieCategory(category, emptyList())
                                    } else {
                                        val movies = response.search?.filter { it.hasValidPoster }?.map { dto ->
                                            Movie(
                                                imdbId = dto.imdbId,
                                                title = dto.title,
                                                year = dto.year,
                                                posterUrl = dto.poster ?: ""
                                            )
                                        } ?: emptyList()
                                        putInCache(categoryCache, cacheKey, movies, categoryTtlMs())
                                        MovieCategory(category, movies)
                                    }
                                } catch (e: Exception) {
                                    Log.e(TAG, "Error fetching category: $category", e)
                                    MovieCategory(category, emptyList())
                                }
                            }
                        }
                    }.awaitAll()
                }
                .flatten()
        }
    }
    
    override suspend fun searchMovies(query: String): Result<List<Movie>> {
        val cacheKey = buildSearchKey(query)
        
        val cached = getFromCache(searchCache, cacheKey)
        if (cached != null) {
            Log.d(TAG, "Cache HIT for search: $query")
            return Result.Success(cached)
        }
        
        return withContext(Dispatchers.Default) {
            try {
                Log.d(TAG, "Searching for: $query")
                val response = apiService.searchMovies(query, page = 1)
                
                if (response.response == "False") {
                    val errorMsg = response.error ?: "Unknown error"
                    Result.Error(AppError.ApiError(
                        userMessage = "Error: $errorMsg",
                        debugMessage = errorMsg,
                        code = 400
                    ))
                } else {
                    val movies = response.search?.filter { it.hasValidPoster }?.map { dto ->
                        Movie(
                            imdbId = dto.imdbId,
                            title = dto.title,
                            year = dto.year,
                            posterUrl = dto.poster ?: ""
                        )
                    } ?: emptyList()
                    putInCache(searchCache, cacheKey, movies, searchTtlMs())
                    Result.Success(movies)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Error searching: $query", e)
                val appError = if (e is ApiException) e.toAppError() else AppError.UnknownError(
                    userMessage = "Error inesperado. Intenta de nuevo.",
                    debugMessage = "Unhandled exception: ${e.message}",
                    cause = e
                )
                Result.Error(appError)
            }
        }
    }
    
    override suspend fun getMovieById(imdbId: String): Result<Movie?> {
        if (imdbId.isEmpty()) return Result.Success(null)
        
        val cacheKey = buildDetailKey(imdbId)
        val cached = getFromCache(detailCache, cacheKey)
        if (cached != null) {
            Log.d(TAG, "Cache HIT for movie: $imdbId")
            return Result.Success(cached)
        }
        
        return withContext(Dispatchers.Default) {
            try {
                Log.d(TAG, "Fetching movie detail: $imdbId")
                val detail = apiService.getMovieDetail(imdbId)
                
                if (detail.response != "True") {
                    val errorMsg = detail.error ?: "Movie not found"
                    Result.Error(AppError.ApiError(
                        userMessage = "Error: $errorMsg",
                        debugMessage = errorMsg,
                        code = 404
                    ))
                } else {
                    val movie = Movie(
                        imdbId = detail.imdbId,
                        title = detail.title,
                        year = detail.year,
                        posterUrl = detail.poster ?: "",
                        rating = detail.imdbRating ?: "N/A",
                        genre = detail.genre ?: "",
                        plot = detail.plot ?: "",
                        runtime = detail.runtime ?: "",
                        director = detail.director ?: "",
                        actors = detail.actors ?: ""
                    )
                    putInCache(detailCache, cacheKey, movie, detailTtlMs())
                    Result.Success(movie)
                }
            } catch (e: Exception) {
                Log.e(TAG, "Error fetching movie: $imdbId", e)
                val appError = if (e is ApiException) e.toAppError() else AppError.UnknownError(
                    userMessage = "Error inesperado. Intenta de nuevo.",
                    debugMessage = "Unhandled exception: ${e.message}",
                    cause = e
                )
                Result.Error(appError)
            }
        }
    }
    
    fun clearAllCaches() {
        categoryCache.evictAll()
        searchCache.evictAll()
        detailCache.evictAll()
        Log.i(TAG, "All caches cleared")
    }
}