using API.Schema;
using API.Schema.Jobs;
using API.Schema.MangaConnectors;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static Microsoft.AspNetCore.Http.StatusCodes;

namespace API.Controllers;

[ApiVersion(2)]
[ApiController]
[Route("v{v:apiVersion}/[controller]")]
public class SearchController(PgsqlContext context) : Controller
{
    
    /// <summary>
    /// Initiate a search for a Manga on all Connectors
    /// </summary>
    /// <param name="name">Name/Title of the Manga</param>
    /// <response code="200"></response>
    /// <response code="500">Error during Database Operation</response>
    [HttpPost("Name")]
    [ProducesResponseType<Manga[]>(Status200OK, "application/json")]
    [ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
    public IActionResult SearchMangaGlobal([FromBody]string name)
    {
        List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> allManga = new();
        foreach (MangaConnector contextMangaConnector in context.MangaConnectors.Where(connector => connector.Enabled))
            allManga.AddRange(contextMangaConnector.GetManga(name));
        
        List<Manga> retMangas = new();
        foreach ((Manga? manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links, List<MangaAltTitle>? altTitles) in allManga)
        {
            try
            {
                Manga? add = AddMangaToContext(manga, authors, tags, links, altTitles);
                if(add is not null)
                    retMangas.Add(add);
            }
            catch (Exception e)
            {
                return StatusCode(500, e);
            }
        }
        return Ok(retMangas.ToArray());
    }
    
    /// <summary>
    /// Initiate a search for a Manga on a specific Connector
    /// </summary>
    /// <param name="MangaConnectorName">Manga-Connector-ID</param>
    /// <param name="SearchName">Name/Title of the Manga</param>
    /// <response code="200"></response>
    /// <response code="404">MangaConnector with ID not found</response>
    /// <response code="406">MangaConnector with ID is disabled</response>
    /// <response code="500">Error during Database Operation</response>
    [HttpPost("{MangaConnectorName}/{SearchName}")]
    [ProducesResponseType<Manga[]>(Status200OK, "application/json")]
    [ProducesResponseType(Status404NotFound)]
    [ProducesResponseType(Status406NotAcceptable)]
    [ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
    public IActionResult SearchManga(string MangaConnectorName, string SearchName)
    {
        MangaConnector? connector = context.MangaConnectors.Find(MangaConnectorName);
        if (connector is null)
            return NotFound();
        else if (connector.Enabled is false)
            return StatusCode(406);
        
        (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] mangas = connector.GetManga(SearchName);
        List<Manga> retMangas = new();
        foreach ((Manga? manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links, List<MangaAltTitle>? altTitles) in mangas)
        {
            try
            {
                Manga? add = AddMangaToContext(manga, authors, tags, links, altTitles);
                if(add is not null)
                    retMangas.Add(add);
            }
            catch (Exception e)
            {
                return StatusCode(500, e.Message);
            }
        }

        return Ok(retMangas.ToArray());
    }

    /// <summary>
    /// Returns Manga from MangaConnector associated with URL
    /// </summary>
    /// <param name="url">Manga-Page URL</param>
    /// <response code="200"></response>
    /// <response code="300">Multiple connectors found for URL</response>
    /// <response code="400">No Manga at URL</response>
    /// <response code="404">No connector found for URL</response>
    /// <response code="500">Error during Database Operation</response>
    [HttpPost("Url")]
    [ProducesResponseType<Manga>(Status200OK, "application/json")]
    [ProducesResponseType(Status300MultipleChoices)]
    [ProducesResponseType(Status400BadRequest)]
    [ProducesResponseType(Status404NotFound)]
    [ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
    public IActionResult GetMangaFromUrl([FromBody]string url)
    {
        List<MangaConnector> connectors = context.MangaConnectors.AsEnumerable().Where(c => c.ValidateUrl(url)).ToList();
        if (connectors.Count == 0)
            return NotFound();
        else if (connectors.Count > 1)
            return StatusCode(Status300MultipleChoices);

        (Manga manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links, List<MangaAltTitle>? altTitles)? x = connectors.First().GetMangaFromUrl(url);
        if (x is null)
            return BadRequest();
        try
        {
            Manga? add = AddMangaToContext(x.Value.manga, x.Value.authors, x.Value.tags, x.Value.links, x.Value.altTitles);
            if (add is not null)
                return Ok(add);
            return StatusCode(500);
        }
        catch (Exception e)
        {
            return StatusCode(500, e.Message);
        }
    }
    
    private Manga? AddMangaToContext(Manga? manga, List<Author>? authors, List<MangaTag>? tags, List<Link>? links,
        List<MangaAltTitle>? altTitles)
    {
        if (manga is null)
            return null;
        
        Manga? existing = context.Manga.FirstOrDefault(m =>
            m.MangaId == manga.MangaId);
        
        if (tags is not null)
        {
            IEnumerable<MangaTag> mergedTags = tags.Select(mt =>
            {
                MangaTag? inDb = context.Tags.FirstOrDefault(t => t.Equals(mt));
                return inDb ?? mt;
            });
            manga.MangaTags = mergedTags.ToList();
            IEnumerable<MangaTag> newTags = manga.MangaTags.Where(mt => !context.Tags.Any(t => t.Tag.Equals(mt.Tag)));
            context.Tags.AddRange(newTags);
        }

        if (authors is not null)
        {
            IEnumerable<Author> mergedAuthors = authors.Select(ma =>
            {
                Author? inDb = context.Authors.FirstOrDefault(a => a.AuthorName == ma.AuthorName);
                return inDb ?? ma;
            });
            manga.Authors = mergedAuthors.ToList();
            IEnumerable<Author> newAuthors = manga.Authors.Where(ma => !context.Authors.Any(a =>
                a.AuthorName == ma.AuthorName));
            context.Authors.AddRange(newAuthors);
        }

        if (links is not null)
        {
            IEnumerable<Link> mergedLinks = links.Select(ml =>
            {
                Link? inDb = context.Link.FirstOrDefault(l =>
                    l.LinkProvider == ml.LinkProvider && l.LinkUrl == ml.LinkUrl);
                return inDb ?? ml;
            });
            manga.Links = mergedLinks.ToList();
            IEnumerable<Link> newLinks = manga.Links.Where(ml => !context.Link.Any(l =>
                l.LinkProvider == ml.LinkProvider && l.LinkUrl == ml.LinkUrl));
            context.Link.AddRange(newLinks);
        }

        if (altTitles is not null)
        {
            IEnumerable<MangaAltTitle> mergedAltTitles = altTitles.Select(mat =>
            {
                MangaAltTitle? inDb = context.AltTitles.FirstOrDefault(at =>
                    at.Language == mat.Language && at.Title == mat.Title);
                return inDb ?? mat;
            });
            manga.AltTitles = mergedAltTitles.ToList();
            IEnumerable<MangaAltTitle> newAltTitles = manga.AltTitles.Where(mat =>
                !context.AltTitles.Any(at => at.Language == mat.Language && at.Title == mat.Title));
            context.AltTitles.AddRange(newAltTitles);
        }
        
        existing?.UpdateWithInfo(manga);
        if(existing is not null)
            context.Manga.Update(existing);
        else
            context.Manga.Add(manga);

        context.Jobs.Add(new DownloadMangaCoverJob(manga.MangaId));
        context.Jobs.Add(new RetrieveChaptersJob(0, manga.MangaId));

        context.SaveChanges();
        return existing ?? manga;
    }
}