using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using HaWeb.Models;
using HaWeb.FileHelpers;
using HaDocument.Interfaces;
using HaDocument.Models;
using HaXMLReader.Interfaces;
using System.Collections.Specialized;
using HaWeb.XMLParser;
using HaWeb.Settings.ParsingState;
using System.Text;
using HaWeb.Settings.ParsingRules;
using System.Linq;//AsParallel, ToList
using System.Collections.Generic;//Dictionary

namespace HaWeb.Controllers;

public class SucheController : Controller {
    private IHaDocumentWrappper _lib;
    private IReaderService _readerService;
    private IXMLService _xmlService;
    private int _lettersForPage;

    public SucheController(IHaDocumentWrappper lib, IReaderService readerService, IXMLService service, IConfiguration config) {
        _lib = lib;
        _readerService = readerService;
        _xmlService = service;
        _lettersForPage = config.GetValue<int>("LettersOnPage");
    }
    
    // Letter Search
    [Route("/HKB/Suche/Briefe/")]
    public IActionResult Briefe(string search, int page = 0, bool? comments = false) {
        var lib = _lib.GetLibrary();

        // Error checking
        if (search == null) return _error404();
        if (String.IsNullOrWhiteSpace(search)) 
            return View("~/Views/HKB/Dynamic/Suche.cshtml", new SucheViewModel(SearchType.Letters, SearchResultType.InvalidSearchTerm, comments, page, null, search, null, null, null, null));
        search = search.Trim();

        // Letter & comment search and search result creation
        var resletter = _xmlService.SearchCollection("letters", search, _readerService, null);
        List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? rescomments = null;
        if (comments == true) 
            rescomments = _xmlService.SearchCollection("marginals", search, _readerService, lib);
        
        // Error checking
        if ((resletter == null || !resletter.Any()) && (rescomments == null || !rescomments.Any())) 
            return View("~/Views/HKB/Dynamic/Suche.cshtml", new SucheViewModel(SearchType.Letters, SearchResultType.NotFound, comments, page, null, search, null, null, null, null));
        
        // Metadata aquisition & sorting
        List<Meta>? metas = new List<Meta>();
        if (resletter != null) 
            metas.AddRange(
                resletter
                    .Select(x => x.Index)
                    .Where(x => lib.Metas.ContainsKey(x))
                    .Select(x => lib.Metas[x])
            );
        if (rescomments != null) 
            metas.AddRange( 
                rescomments
                    .Where(x => lib.Marginals.ContainsKey(x.Index))
                    .Select(x => lib.Marginals[x.Index])
                    .Where(x => lib.Metas.ContainsKey(x.Letter))
                    .Select(x => lib.Metas[x.Letter])
            );

        // Return
        return _paginateSendLettersComments(lib, page, search, comments, SearchResultType.Success, metas.Distinct().ToList(), resletter, rescomments);
    }

    // Register & Bibliography Search
    [Route("/HKB/Suche/Register/")]
    public IActionResult Register(string search) {
        var lib = _lib.GetLibrary();

        // Error checking
        if (search == null) return _error404();
        if (String.IsNullOrWhiteSpace(search)) 
            return _paginateSendRegister(lib, search, SearchType.Register, SearchResultType.InvalidSearchTerm, null);
        search = search.Trim();

        // Search
        List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? res = null;
        
        res = _xmlService.SearchCollection("register-comments", search, _readerService, lib);
        if (res == null || !res.Any()) 
            return _paginateSendRegister(lib, search, SearchType.Register, SearchResultType.NotFound, null);

        // Return 
        return _paginateSendRegister(lib, search, SearchType.Register, SearchResultType.Success, _createComments("neuzeit", res.Select((x) => (x.Index, x.Results.Select((y) => y.Identifier).ToList())).OrderBy(x => x.Index).ToList()));

    }

    [Route("/HKB/Suche/Forschung/")]
    public IActionResult Science(string search) {
        var lib = _lib.GetLibrary();

        // Error checking
        if (search == null) return _error404();
        if (String.IsNullOrWhiteSpace(search)) 
            return _paginateSendRegister(lib, search, SearchType.Science, SearchResultType.InvalidSearchTerm, null);
        search = search.Trim();

        // Search
        List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? res = null;
        res = _xmlService.SearchCollection("forschung-comments", search, _readerService, lib);
        if (res == null || !res.Any()) 
            return _paginateSendRegister(lib, search, SearchType.Science, SearchResultType.NotFound, null);

        // Return 
        return _paginateSendRegister(lib, search, SearchType.Science, SearchResultType.Success, _createComments("neuzeit", res.Select((x) => (x.Index, x.Results.Select((y) => y.Identifier).ToList())).OrderBy(x => x.Index).ToList()));

    }

    private IActionResult _paginateSendLettersComments(
        ILibrary lib,
        int page,
        string search,
        bool? comments,
        SearchResultType SRT,
        List<Meta>? metas,
        List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? resletters,
        List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? rescomments
    ) {
        // Sorting, get Pages & Error Checking
        var metasbyyear = metas!.Distinct().ToLookup(x => x.Sort.Year).OrderBy(x => x.Key).ToList();
        var pages = IndexController.Paginate(metasbyyear, _lettersForPage);
        if (pages != null && page >= pages.Count) return _error404();
        if (pages == null && page > 0) return _error404();

        List<(int Year, List<BriefeMetaViewModel> LetterList)>? letters = null;
        // Select & Parse Metadata for Letters to be shown on the selected Page
        if (pages != null && metasbyyear != null) 
            letters = metasbyyear
                .Where(x => x.Key >= pages[page].StartYear && x.Key <= pages[page].EndYear)
                .Select(x => (x.Key, x
                    .Select(y => Briefecontroller.GenerateMetaViewModel(lib, y))
                    .OrderBy(x => x.Meta.Sort)
                    .ThenBy(x => x.Meta.Order)
                    .ToList()))
                .ToList();
        
        // Generate Search results & previews
        Dictionary<string, List<SearchResult>>? searchResults = new Dictionary<string, List<SearchResult>>();
        Dictionary<string, List<(Marginal, string)>>? parsedMarginals = null;
        if (resletters != null)
            foreach (var res in resletters) {
                if (!searchResults.ContainsKey(res.Index))
                    searchResults.Add(res.Index, new List<SearchResult>());
                foreach (var r in res.Results) {
                    if(!searchResults[res.Index].Where(x => x.Page == r.Page && x.Line == r.Line).Any())
                        searchResults[res.Index].Add(new SearchResult(search, res.Index) { Page = r.Page, Line = r.Line, Preview = r.Preview });
                }
                if (searchResults[res.Index].Any()) {
                    searchResults[res.Index] = searchResults[res.Index].OrderBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.Page)).ThenBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.Line)).ToList();
                }
            }
        if (rescomments != null) {
            var marginals = rescomments.Where(x => lib.Marginals.ContainsKey(x.Index)).Select(x => lib.Marginals[x.Index]).ToLookup(x => x.Letter);
            var shownletters = letters!.SelectMany(x => x.LetterList.Select(y => y.Meta.Index)).ToHashSet();
            var shownmarginals = marginals!.Where(x => shownletters.Contains(x.Key)).Select(x => (x.Key, x.ToList())).ToList();
            var previews = _xmlService != null ? _xmlService.GetPreviews(shownmarginals, _readerService ,lib) : null;            
            if (previews != null)
                foreach (var p in previews) {
                    if (!searchResults.ContainsKey(p.Index))
                        searchResults.Add(p.Index, new List<SearchResult>());
                    foreach (var res in p.Results) {
                        if (!searchResults[p.Index].Where(x => x.Page == res.Page && x.Line == res.Line).Any())
                            searchResults[p.Index].Add(new SearchResult(search, p.Index) { Page = res.Page, Line = res.Line, Preview = res.Preview });
                    }
                    if (searchResults[p.Index].Any()) {
                        searchResults[p.Index] = searchResults[p.Index].OrderBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.Page)).ThenBy(x => HaWeb.HTMLHelpers.ConversionHelpers.RomanOrNumberToInt(x.Line)).ToList();
                    }
                }

            // Parse Marginals
            foreach (var l in marginals) {
                if (parsedMarginals == null && l.Any()) 
                    parsedMarginals = new Dictionary<string, List<(Marginal, string)>>();
                if (l.Any()) {
                    var list = new List<(Marginal, string)>();
                    foreach (var c in l) {
                        var sb = new StringBuilder();
                        var rd = _readerService.RequestStringReader(c.Element);
                        var st = new TextState(lib, _readerService, lib.Metas[c.Letter], null, null, null);
                        new HaWeb.HTMLParser.XMLHelper<HaWeb.Settings.ParsingState.TextState>(st, rd, sb, TextRules.OTagRules, null, TextRules.CTagRules, TextRules.TRules, TextRules.WhitespaceRules);
                        new HaWeb.HTMLHelpers.LinkHelper(st.Lib, rd, sb, false);
                        rd.Read(); 
                        list.Add((c, sb.ToString()));
                    }
                    parsedMarginals!.Add(l.Key, list);
                }
            }
        }

        // Model Init & Return
        var model = new SucheViewModel(SearchType.Letters, SearchResultType.Success, comments, page, _paginate(pages), search, searchResults, letters, null, parsedMarginals);
        return View("~/Views/HKB/Dynamic/Suche.cshtml", model);
    }

    private IActionResult _paginateSendRegister(
        ILibrary lib,
        string activeSearch,
        SearchType ST,
        SearchResultType SRT,
        List<CommentModel> comments) {
            // Model init & return
            var model = new SucheViewModel(ST, SRT, null, 0, null, activeSearch, null, null, comments, null);
            return View("~/Views/HKB/Dynamic/Suche.cshtml", model);
    }

    private List<string>? _paginate(List<(int StartYear, int EndYear)>? pages) {
        return pages != null ? pages.Select(x => {
            if (x.StartYear != x.EndYear) 
                return x.StartYear + "–" + x.EndYear;
            else
                return x.StartYear.ToString();
        }).ToList() : null;
    }

    // Select and parse comments to be shown on a page
    private List<CommentModel> _createComments(string category, List<(string, List<string>)>? comments) {
        var lib = _lib.GetLibrary();
        var res = new List<CommentModel>();
        if (comments == null) return res;
        foreach (var comm in comments) {
            var commobj = lib.Comments[comm.Item1];
            var parsedComment = HTMLHelpers.CommentHelpers.CreateHTML(lib, _readerService, commobj, category, Settings.ParsingState.CommentType.Comment);
            List<string>? parsedSubComments = new List<string>();
            var distinctList = comm.Item2.Distinct().ToList();
            foreach (var subcomm in distinctList) {
                if (subcomm != comm.Item1) {
                    var subcommobj = lib.SubCommentsByID[subcomm];
                    parsedSubComments.Add(HTMLHelpers.CommentHelpers.CreateHTML(lib, _readerService, subcommobj, category, Settings.ParsingState.CommentType.Subcomment));
                }
            }
            res.Add(new CommentModel(parsedComment, parsedSubComments));
        }
        return res;
    }

    private IActionResult _error404() {
        Response.StatusCode = 404;
        return Redirect("/Error404");
    }
}
