using Butter.Dtos;
using Butter.Dtos.Album;
using Butter.Types;
using Lactose.Mapper;
using Lactose.Models;
using Lactose.Repositories;
using Lactose.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Lactose.Controllers;

[ApiController]
[Authorize]
[Route("api/[controller]")]
public class AlbumController(
    //TODO: Add logging
    //ILogger<AlbumController> logger,
    LactoseAuthService authService,
    IAlbumRepository albumRepository,
    IAssetRepository assetRepository
) : ControllerBase {
    [HttpGet("{id}")]
    public ActionResult<AlbumFullDto> Get([FromRoute] Guid id) {
        Guid?        uid         = authService.GetUserData(User)?.Id;
        EAccessLevel accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;

        var album = albumRepository.Find(id);

        // If the album does not exist, return a 404
        if (album == null) return NotFound();

        // If the album has no assets, return an empty list
        if (album.Assets == null) return Ok(album.ToAlbumFullDto());

        //Storage for assets that will be sent out;
        List<Asset> assets = new();

        switch (accessLevel) {
            case EAccessLevel.User:
                // Only publicly shared assets or owned by user
                assets.AddRange(album.Assets.Where(asset => asset.IsPubliclyShared || asset.OwnerId == uid));
                // Add shared assets
                assets.AddRange(album.Assets.Where(asset => asset.SharedWith!.Any(user => user.Id == uid)));
                break;
            case EAccessLevel.Curator:
            case EAccessLevel.Admin:
                // All assets
                assets.AddRange(album.Assets);
                break;
        }

        // changes the retrieved album to the new filtered
        album.Assets = assets;
        return Ok(album.ToAlbumFullDto());
    }

    [HttpGet]
    public ActionResult<List<AlbumPreviewDto>> Search([FromQuery] PagedSearchParametersDto pagingOptions) {
        var uid         = authService.GetUserData(User)?.Id;
        var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
        
        if(pagingOptions.Page < 0 || pagingOptions.PageSize < 0) return BadRequest();
        
        var albums = albumRepository.SearchQuery(pagingOptions.Search ?? string.Empty, pagingOptions.Page, pagingOptions.PageSize).ToList();
        
        switch (accessLevel) {
            default:
            case EAccessLevel.User:
                List<Album> filteredAlbums;
                // Only publicly shared albums or owned by user
                filteredAlbums = albums.Where(album => album.Assets != null && (album.UserOwnerId == uid || album.Assets.Any(asset => asset.IsPubliclyShared))).ToList();
                // Add shared albums
                filteredAlbums.AddRange(albums.Where(album => album.Assets != null && album.Assets.Any(x => x.SharedWith!.Any(user => user.Id == uid))));
                return Ok(filteredAlbums.Select(x => x.ToAlbumPreviewDto()).ToList());
            case EAccessLevel.Curator:
            case EAccessLevel.Admin:
                // All albums
                return Ok(albums.Select(x => x.ToAlbumPreviewDto()).ToList());
        }
    }

    [HttpPost("{id}")]
    [Authorize(Roles = "Admin, Curator")]
    public ActionResult Update([FromRoute] Guid id, [FromBody] AlbumUpdateDto albumDto) {
        Guid? uid         = authService.GetUserData(User)?.Id;
        var   accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;

        var album = albumRepository.Find(id);

        // If the album does not exist, return a 404
        if (album == null) return NotFound();

        switch (accessLevel) {
            case EAccessLevel.User:
                // Users can't update albums
                return Forbid();
            case EAccessLevel.Curator:
                // Curators can only update albums they own
                if (album.UserOwnerId != uid) return Forbid();
                break;
            case EAccessLevel.Admin:
                // Admins can update any album
                break;
        }

        // Update album properties
        album.Title         = albumDto.Name ?? album.Title;
        album.UpdatedAt     = DateTime.Now;
        album.PersonOwnerId = albumDto.Person ?? album.PersonOwnerId;
        album.UserOwnerId   = albumDto.Owner ?? album.UserOwnerId;
        album.Assets        = albumDto.Assets != null ? assetRepository.FindBulk(albumDto.Assets).ToList() : album.Assets;

        // Save changes
        albumRepository.Update(album);

        return Ok();
    }

    [HttpPost]
    [Authorize(Roles = "Admin, Curator")]
    public ActionResult BulkUpdate([FromBody] BulkDto<AlbumUpdateDto> bulkDto) {
        var uid         = authService.GetUserData(User)?.Id;
        var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;

        var albums = albumRepository.FindBulk(bulkDto.Ids).ToList();

        switch (accessLevel) {
            case EAccessLevel.User:
                // Users can't update albums
                return Forbid();
            case EAccessLevel.Curator:
                // Curators can only update albums they own
                albums = albums.Where(x => x.UserOwnerId == uid).ToList();
                break;
            case EAccessLevel.Admin:
                // Admins can update any album
                break;
        }

        foreach (var album in albums) {
            album.Title = bulkDto.Data.Name ?? album.Title;
            album.UpdatedAt = DateTime.Now;
            album.PersonOwnerId = bulkDto.Data.Person ?? album.PersonOwnerId;
            album.UserOwnerId = bulkDto.Data.Owner ?? album.UserOwnerId;
            album.Assets = bulkDto.Data.Assets != null ? assetRepository.FindBulk(bulkDto.Data.Assets).ToList() : album.Assets;
        }

        albumRepository.UpdateBulk(albums);
        albumRepository.Save();

        return Ok();
    }

    [HttpPut]
    [Authorize(Roles = "Admin, Curator")]
    public ActionResult Create([FromBody] AlbumCreateDto albumDto) {
        var uid         = authService.GetUserData(User)?.Id;
        var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;

        // If not admin, the owner of a new album is the current user
        albumDto.Owner = accessLevel == EAccessLevel.Admin ? albumDto.Owner : uid;

        var album = new Album {
            Id            = Guid.NewGuid(),
            Title         = albumDto.Name,
            CreatedAt     = DateTime.Now,
            PersonOwnerId = albumDto.Person ?? null,
            UserOwnerId   = albumDto.Owner,
            Assets        = albumDto.Assets != null ? assetRepository.FindBulk(albumDto.Assets).ToList() : new List<Asset>()
        };

        albumRepository.Insert(album);
        albumRepository.Save();

        return Ok();
    }

    [HttpDelete]
    [Authorize(Roles = "Admin, Curator")]
    public ActionResult BulkDelete([FromBody] List<Guid> albumIds) {
        var uid         = authService.GetUserData(User)?.Id;
        var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;

        var albums = albumRepository.FindBulk(albumIds).ToList();

        // If the user is not an admin, filter out albums they do not own
        if (accessLevel != EAccessLevel.Admin) { albums = albums.Where(x => x.UserOwnerId == uid).ToList(); }

        // If no albums are left after filtering, return a 403
        if (albumIds.Count == 0) return Forbid();

        albums.ForEach(albumRepository.Remove);
        albumRepository.Save();

        return Ok();
    }

    [HttpDelete("{id}")]
    [Authorize(Roles = "Admin, Curator")]
    public ActionResult Delete(Guid id) {
        var uid         = authService.GetUserData(User)?.Id;
        var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;

        var album = albumRepository.Find(id);

        // If the album does not exist, return a 404
        if (album == null) return NotFound();
        // If the user is not the owner or an admin, return a 403
        if (accessLevel != EAccessLevel.Admin && album.UserOwnerId != uid) return Forbid();

        albumRepository.Remove(album);

        return Ok();
    }
}
