using Butter.Dtos;
using Butter.Dtos.Tag;
using Lactose.Mapper;
using Lactose.Models;
using Lactose.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;

namespace Lactose.Controllers;

//TODO: logging

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class TagController(
    //TODO: add logging
    //ILogger<TagController> logger,
    ITagRepository tagRepository
) : ControllerBase {
    [HttpGet("{id}")]
    public ActionResult<TagDto> Get(Guid id, bool children = false, bool ancestors = false) {
        Tag? tag = tagRepository.Find(id);

        if (tag == null) { return NotFound(); }
        var tagDto = TagsMapper.ToTagDto(tag);
        tagDto.Parent = tag.Parent == null ? null : TagsMapper.ToTagDto(tag.Parent);

        if (children) {
            tagDto.Children = [];
            tagRepository.GetChildren(tag.Id).ForEach(t => tagDto.Children.Add(TagsMapper.ToTagDto(t)));
        }

        //TODO: Protect from overflow
        if (ancestors) {
            Tag?      index         = tag.Parent;
            TagDto?   indexDto      = tagDto;
            List<Tag> ancestorsTags = [];

            // This is redundant, because circular references shouldn't be present
            while (index != null && !ancestorsTags.Contains(tag)) {
                ancestorsTags.Add(index);
                indexDto.Parent = TagsMapper.ToTagDto(index);
                index           = index.Parent;
                indexDto        = indexDto.Parent;
            }
        }

        return tagDto;
    }

    [HttpGet]
    public ActionResult<List<TagDto>> GetAll(PagedSearchParametersDto pagedSearch) {
        pagedSearch.Search ??= string.Empty;
        if(pagedSearch.Page < 0) { return BadRequest("Page must be greater than 0"); }
        if(pagedSearch.PageSize < 0) { return BadRequest("PageSize must be greater than 0"); }
        
        List<Tag>    tags    = tagRepository.Search(pagedSearch.Search, pagedSearch.Page, pagedSearch.PageSize);
        List<TagDto> tagsDto = [];

        tags.ForEach(
            t => {
                var tagDto = TagsMapper.ToTagDto(t);
                tagDto.Parent = t.Parent == null ? null : TagsMapper.ToTagDto(t.Parent);
                tagsDto.Add(tagDto);
            }
        );

        return tagsDto;
    }

    [Authorize(Roles = "Admin, Curator")]
    [HttpPost("{id}")]
    public IStatusCodeActionResult Update([FromRoute] Guid id, [FromBody] TagUpdateDto tagDto) {

        IStatusCodeActionResult result = UpdateTag(id, tagDto.Name, tagDto.Parent);

        if (result.StatusCode == StatusCodes.Status200OK) { tagRepository.Save(); }

        return result;
    }

    [Authorize(Roles = "Admin, Curator")]
    [HttpPost]
    public IStatusCodeActionResult BulkUpdate([FromBody] BulkDto<TagUpdateDto> tagBulkDto) {

        foreach (Guid id in tagBulkDto.Ids) {
            IStatusCodeActionResult result = UpdateTag(id, tagBulkDto.Data.Name, tagBulkDto.Data.Parent);

            if (result.StatusCode != StatusCodes.Status200OK) { return result; }
        }

        tagRepository.Save();
        return Ok();
    }

    [Authorize(Roles = "Admin, Curator")]
    [HttpDelete("{id}")]
    public ActionResult Delete([FromRoute] Guid id) {
        IStatusCodeActionResult result = DeleteTag(id);

        if (result.StatusCode == StatusCodes.Status200OK) { tagRepository.Save(); }

        return (ActionResult)result;
    }

    [Authorize(Roles = "Admin, Curator")]
    [HttpDelete]
    public IActionResult BulkDelete([FromBody] List<Guid> ids) {
        foreach (Guid id in ids) {
            IStatusCodeActionResult result = DeleteTag(id);

            if (result.StatusCode != StatusCodes.Status200OK) { return result; }
        }

        tagRepository.Save();
        return Ok();
    }

    [Authorize(Roles = "Admin, Curator")]
    [HttpPut]
    public ActionResult Create([FromBody] TagCreateDto tagDto) {

        if (tagRepository.FindByName(tagDto.Name) != null) { return Conflict($"Tag with name {tagDto.Name} already exist"); }

        var tag = new Tag() {
            Name = tagDto.Name
        };

        if (tagDto.Parent != null && tagRepository.Find(tagDto.Parent.Value) == null) {
            return BadRequest($"No Tag with id {tagDto.Parent} found");
        }

        tag.ParentId = tagDto.Parent;
        tagRepository.Insert(tag);
        tagRepository.Save();
        return Ok();
    }

    IStatusCodeActionResult DeleteTag(Guid id) {
        Tag? tag = tagRepository.Find(id);

        if (tag == null) { return NotFound($"Tag {id} not found"); }
        tagRepository.Delete(tag);
        return Ok();
    }


    IStatusCodeActionResult UpdateTag(Guid id, string? name, Guid? parentId) {

        Tag? tag = tagRepository.Find(id);

        if (tag == null) { return NotFound($"Tag {id} not found"); }

        if (name != null) {
            Tag? foundTag = tagRepository.FindByName(name);

            if (foundTag != null && foundTag.Id != tag.Id) { return Conflict($"Tag with name '{name}' already exists"); }

            tag.Name = name;
        }

        if (parentId == null) { return Ok(); }

        if (parentId.Value.Equals(Guid.Empty)) { tag.Parent = null; } else {
            Tag? parent = tagRepository.Find(parentId.Value);

            if (parent == null) { return NotFound($"Parent tag on {id} was not found"); }
            tag.Parent = parent;
        }

        return Ok();
    }
}