// <copyright file="DatabaseViewsController.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>

namespace Jellyfin.Api.Controllers;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities.Views;
using MediaBrowser.Common.Api;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

/// <summary>
/// Exposes the PostgreSQL reporting views as read-only API endpoints.
/// All endpoints require administrator privileges.
/// </summary>
[Route("DatabaseViews")]
[Authorize(Policy = Policies.RequiresElevation)]
public class DatabaseViewsController : BaseJellyfinApiController
{
    private readonly IDbContextFactory<JellyfinDbContext> _dbFactory;

    /// <summary>
    /// Initializes a new instance of the <see cref="DatabaseViewsController"/> class.
    /// </summary>
    /// <param name="dbFactory">The EF Core context factory.</param>
    public DatabaseViewsController(IDbContextFactory<JellyfinDbContext> dbFactory)
    {
        _dbFactory = dbFactory;
    }

    /// <summary>
    /// Gets a flat list of all media streams (audio + video columns merged).
    /// Maps to library."MediaStreamInfos_v".
    /// </summary>
    /// <param name="itemId">Optional item ID filter.</param>
    /// <param name="streamType">Optional stream type filter (0=Audio, 1=Video, 2=Subtitle).</param>
    /// <param name="startIndex">Record index to start at.</param>
    /// <param name="limit">Maximum records to return (1-1000, default 100).</param>
    /// <returns>List of media stream rows.</returns>
    [HttpGet("MediaStreamInfos")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<MediaStreamInfoView>>> GetMediaStreamInfos(
        [FromQuery] Guid? itemId = null,
        [FromQuery] int? streamType = null,
        [FromQuery] int startIndex = 0,
        [FromQuery] int limit = 100)
    {
        limit = Math.Clamp(limit, 1, 1000);
        await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);

        var query = db.MediaStreamInfoViews.AsNoTracking();
        if (itemId.HasValue)
        {
            var id = itemId.Value;
            query = query.Where(x => x.ItemId.HasValue && x.ItemId.Value.Equals(id));
        }

        if (streamType.HasValue)
        {
            query = query.Where(x => x.StreamType == streamType.Value);
        }

        IEnumerable<MediaStreamInfoView> results = await query
            .OrderBy(x => x.ItemId)
            .ThenBy(x => x.StreamIndex)
            .Skip(startIndex)
            .Take(limit)
            .ToListAsync()
            .ConfigureAwait(false);

        return Ok(results);
    }

    /// <summary>
    /// Gets video items joined with primary stream technical details.
    /// Maps to library."VideoItems_v".
    /// </summary>
    /// <param name="type">Optional partial type filter (e.g. "Movie", "Episode").</param>
    /// <param name="minHeight">Optional minimum video height (e.g. 2160 for 4K).</param>
    /// <param name="hdrFormat">Optional HDR format ("Dolby Vision", "HDR10+", "HDR10", "HLG", "SDR").</param>
    /// <param name="startIndex">Record index to start at.</param>
    /// <param name="limit">Maximum records to return (1-1000, default 100).</param>
    /// <returns>List of video item rows.</returns>
    [HttpGet("VideoItems")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<VideoItemView>>> GetVideoItems(
        [FromQuery] string? type = null,
        [FromQuery] int? minHeight = null,
        [FromQuery] string? hdrFormat = null,
        [FromQuery] int startIndex = 0,
        [FromQuery] int limit = 100)
    {
        limit = Math.Clamp(limit, 1, 1000);
        await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);

        var query = db.VideoItemViews.AsNoTracking();
        if (!string.IsNullOrEmpty(type))
        {
            query = query.Where(x => x.Type != null && x.Type.Contains(type));
        }

        if (minHeight.HasValue)
        {
            query = query.Where(x => x.Height >= minHeight.Value);
        }

        if (!string.IsNullOrEmpty(hdrFormat))
        {
            query = query.Where(x => x.HDRFormat == hdrFormat);
        }

        IEnumerable<VideoItemView> results = await query
            .OrderBy(x => x.Name)
            .Skip(startIndex)
            .Take(limit)
            .ToListAsync()
            .ConfigureAwait(false);

        return Ok(results);
    }

    /// <summary>
    /// Gets per-user playback history with calculated progress percentages.
    /// Maps to library."UserPlaybackHistory_v".
    /// </summary>
    /// <param name="username">Optional exact username filter.</param>
    /// <param name="itemId">Optional item ID filter.</param>
    /// <param name="inProgressOnly">When true, returns only items started but not fully played.</param>
    /// <param name="startIndex">Record index to start at.</param>
    /// <param name="limit">Maximum records to return (1-1000, default 100).</param>
    /// <returns>List of playback history rows.</returns>
    [HttpGet("PlaybackHistory")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<UserPlaybackHistoryView>>> GetPlaybackHistory(
        [FromQuery] string? username = null,
        [FromQuery] Guid? itemId = null,
        [FromQuery] bool inProgressOnly = false,
        [FromQuery] int startIndex = 0,
        [FromQuery] int limit = 100)
    {
        limit = Math.Clamp(limit, 1, 1000);
        await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);

        var query = db.UserPlaybackHistoryViews.AsNoTracking();
        if (!string.IsNullOrEmpty(username))
        {
            query = query.Where(x => x.Username == username);
        }

        if (itemId.HasValue)
        {
            var id = itemId.Value;
            query = query.Where(x => x.ItemId.HasValue && x.ItemId.Value.Equals(id));
        }

        if (inProgressOnly)
        {
            query = query.Where(x => x.Played != true && x.ProgressPct > 0);
        }

        IEnumerable<UserPlaybackHistoryView> results = await query
            .OrderByDescending(x => x.LastPlayedDate)
            .Skip(startIndex)
            .Take(limit)
            .ToListAsync()
            .ConfigureAwait(false);

        return Ok(results);
    }

    /// <summary>
    /// Gets items with their external provider IDs (IMDb, TMDB, TVDB, etc.) in named columns.
    /// Maps to library."ItemProviders_v".
    /// </summary>
    /// <param name="type">Optional partial type filter.</param>
    /// <param name="imdbId">Optional exact IMDb ID filter.</param>
    /// <param name="tmdbId">Optional exact TMDB ID filter.</param>
    /// <param name="tvdbId">Optional exact TVDB ID filter.</param>
    /// <param name="startIndex">Record index to start at.</param>
    /// <param name="limit">Maximum records to return (1-1000, default 100).</param>
    /// <returns>List of item provider rows.</returns>
    [HttpGet("ItemProviders")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<ItemProviderView>>> GetItemProviders(
        [FromQuery] string? type = null,
        [FromQuery] string? imdbId = null,
        [FromQuery] string? tmdbId = null,
        [FromQuery] string? tvdbId = null,
        [FromQuery] int startIndex = 0,
        [FromQuery] int limit = 100)
    {
        limit = Math.Clamp(limit, 1, 1000);
        await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);

        var query = db.ItemProviderViews.AsNoTracking();
        if (!string.IsNullOrEmpty(type))
        {
            query = query.Where(x => x.Type != null && x.Type.Contains(type));
        }

        if (!string.IsNullOrEmpty(imdbId))
        {
            query = query.Where(x => x.ImdbId == imdbId);
        }

        if (!string.IsNullOrEmpty(tmdbId))
        {
            query = query.Where(x => x.TmdbId == tmdbId);
        }

        if (!string.IsNullOrEmpty(tvdbId))
        {
            query = query.Where(x => x.TvdbId == tvdbId);
        }

        IEnumerable<ItemProviderView> results = await query
            .OrderBy(x => x.Name)
            .Skip(startIndex)
            .Take(limit)
            .ToListAsync()
            .ConfigureAwait(false);

        return Ok(results);
    }

    /// <summary>
    /// Gets cast and crew entries linked to the items they appear in.
    /// Maps to library."ItemPeople_v".
    /// </summary>
    /// <param name="personName">Optional exact person name filter.</param>
    /// <param name="personType">Optional person type filter (e.g. "Actor", "Director").</param>
    /// <param name="itemId">Optional item ID filter.</param>
    /// <param name="startIndex">Record index to start at.</param>
    /// <param name="limit">Maximum records to return (1-1000, default 100).</param>
    /// <returns>List of item person rows.</returns>
    [HttpGet("ItemPeople")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<ItemPersonView>>> GetItemPeople(
        [FromQuery] string? personName = null,
        [FromQuery] string? personType = null,
        [FromQuery] Guid? itemId = null,
        [FromQuery] int startIndex = 0,
        [FromQuery] int limit = 100)
    {
        limit = Math.Clamp(limit, 1, 1000);
        await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);

        var query = db.ItemPersonViews.AsNoTracking();
        if (!string.IsNullOrEmpty(personName))
        {
            query = query.Where(x => x.PersonName == personName);
        }

        if (!string.IsNullOrEmpty(personType))
        {
            query = query.Where(x => x.PersonType == personType);
        }

        if (itemId.HasValue)
        {
            var id = itemId.Value;
            query = query.Where(x => x.ItemId.HasValue && x.ItemId.Value.Equals(id));
        }

        IEnumerable<ItemPersonView> results = await query
            .OrderBy(x => x.PersonName)
            .ThenBy(x => x.SortOrder)
            .Skip(startIndex)
            .Take(limit)
            .ToListAsync()
            .ConfigureAwait(false);

        return Ok(results);
    }

    /// <summary>
    /// Gets aggregate library statistics - one row per item type.
    /// Maps to library."LibrarySummary_v".
    /// </summary>
    /// <returns>List of library summary rows, one per item type.</returns>
    [HttpGet("LibrarySummary")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<LibrarySummaryView>>> GetLibrarySummary()
    {
        await using var db = await _dbFactory.CreateDbContextAsync().ConfigureAwait(false);

        IEnumerable<LibrarySummaryView> results = await db.LibrarySummaryViews
            .AsNoTracking()
            .OrderBy(x => x.Type)
            .ToListAsync()
            .ConfigureAwait(false);

        return Ok(results);
    }
}
