using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using ElmahCore;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Server.Exceptions;
using Server.Helper;
using Shared.Models;

namespace Server.Controllers;

[ApiController]
[Route("[controller]")]
public class SearchdomainController : ControllerBase
{
    private readonly ILogger<SearchdomainController> _logger;
    private readonly IConfiguration _config;
    private SearchdomainManager _domainManager;

    public SearchdomainController(ILogger<SearchdomainController> logger, IConfiguration config, SearchdomainManager domainManager)
    {
        _logger = logger;
        _config = config;
        _domainManager = domainManager;
    }

    /// <summary>
    /// Lists all searchdomains
    /// </summary>
    [HttpGet("/Searchdomains")]
    public ActionResult<SearchdomainListResults> List()
    {
        List<string> results;
        try
        {
            results = _domainManager.ListSearchdomains();
        }
        catch (Exception)
        {
            _logger.LogError("Unable to list searchdomains");
            throw;
        }
        SearchdomainListResults searchdomainListResults = new() {Searchdomains = results};
        return Ok(searchdomainListResults);
    }

    /// <summary>
    /// Creates a new searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    /// <param name="settings">Optional initial settings</param>
    [HttpPost]
    public ActionResult<SearchdomainCreateResults> Create([Required]string searchdomain, [FromBody]SearchdomainSettings settings = new())
    {
        try
        {
            int id = _domainManager.CreateSearchdomain(searchdomain, settings);
            return Ok(new SearchdomainCreateResults(){Id = id, Success = true});
        } catch (Exception)
        {
            _logger.LogError("Unable to create Searchdomain {searchdomain}", [searchdomain]);
            return Ok(new SearchdomainCreateResults() { Id = null, Success = false, Message = $"Unable to create Searchdomain {searchdomain}" });
        }
    }

    /// <summary>
    /// Deletes a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpDelete]
    public ActionResult<SearchdomainDeleteResults> Delete([Required]string searchdomain)
    {
        bool success;
        int deletedEntries;
        string? message = null;
        try
        {
            success = true;
            deletedEntries = _domainManager.DeleteSearchdomain(searchdomain);
        }
        catch (SearchdomainNotFoundException ex)
        {
            _logger.LogError("Unable to delete searchdomain {searchdomain} - not found", [searchdomain]);
            success = false;
            deletedEntries = 0;
            message = $"Unable to delete searchdomain {searchdomain} - not found";
            ElmahExtensions.RaiseError(ex);
        }
        catch (Exception ex)
        {
            _logger.LogError("Unable to delete searchdomain {searchdomain}", [searchdomain]);
            success = false;
            deletedEntries = 0;
            message = ex.Message;
            ElmahExtensions.RaiseError(ex);
        }
        return Ok(new SearchdomainDeleteResults(){Success = success, DeletedEntities = deletedEntries, Message = message});
    }

    /// <summary>
    /// Updates name and settings of a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    /// <param name="newName">Updated name of the searchdomain</param>
    /// <param name="settings">Updated settings of searchdomain</param>
    [HttpPut]
    public ActionResult<SearchdomainUpdateResults> Update([Required]string searchdomain, string newName, [FromBody]SearchdomainSettings? settings)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        if (settings is null)
        {
            Dictionary<string, dynamic> parameters = new()
            {
                {"name", newName},
                {"id", searchdomain_.id}
            };
            searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set name = @name WHERE id = @id", parameters);
        } else
        {
            Dictionary<string, dynamic> parameters = new()
            {
                {"name", newName},
                {"settings", settings},
                {"id", searchdomain_.id}
            };
            searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set name = @name, settings = @settings WHERE id = @id", parameters);            
        }
        return Ok(new SearchdomainUpdateResults(){Success = true});
    }

    /// <summary>
    /// Gets the query cache of a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpGet("Queries")]
    public ActionResult<SearchdomainSearchesResults> GetQueries([Required]string searchdomain)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
        
        return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
    }

    /// <summary>
    /// Executes a query in the searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    /// <param name="query">Query to execute</param>
    /// <param name="topN">Return only the top N results</param>
    /// <param name="returnAttributes">Return the attributes of the object</param>
    [HttpPost("Query")]
    public ActionResult<EntityQueryResults> Query([Required]string searchdomain, [Required]string query, int? topN, bool returnAttributes = false)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        List<(float, string)> results = searchdomain_.Search(query, topN);
        List<EntityQueryResult> queryResults = [.. results.Select(r => new EntityQueryResult
        {
            Name = r.Item2,
            Value = r.Item1,
            Attributes = returnAttributes ? (searchdomain_.entityCache.FirstOrDefault(x => x.name == r.Item2)?.attributes ?? null) : null
        })];
        return Ok(new EntityQueryResults(){Results = queryResults, Success = true });
    }

    /// <summary>
    /// Deletes a query from the query cache
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    /// <param name="query">Query to delete</param>
    [HttpDelete("Query")]
    public ActionResult<SearchdomainDeleteSearchResult> DeleteQuery([Required]string searchdomain, [Required]string query)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
        bool containsKey = searchCache.ContainsKey(query);
        if (containsKey)
        {
            searchCache.Remove(query);
            return Ok(new SearchdomainDeleteSearchResult() {Success = true});
        }
        return Ok(new SearchdomainDeleteSearchResult() {Success = false, Message = "Query not found in search cache"});
    }

    /// <summary>
    /// Updates a query from the query cache
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    /// <param name="query">Query to update</param>
    /// <param name="results">List of results to apply to the query</param>
    [HttpPatch("Query")]
    public ActionResult<SearchdomainUpdateSearchResult> UpdateQuery([Required]string searchdomain, [Required]string query, [Required][FromBody]List<ResultItem> results)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
        bool containsKey = searchCache.ContainsKey(query);
        if (containsKey)
        {
            DateTimedSearchResult element = searchCache[query];
            element.Results = results;
            searchCache[query] = element;
            return Ok(new SearchdomainUpdateSearchResult() {Success = true});
        }
        return Ok(new SearchdomainUpdateSearchResult() {Success = false, Message = "Query not found in search cache"});
    }

    /// <summary>
    /// Get the settings of a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpGet("Settings")]
    public ActionResult<SearchdomainSettingsResults> GetSettings([Required]string searchdomain)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        SearchdomainSettings settings = searchdomain_.settings;
        return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true });
    }

    /// <summary>
    /// Update the settings of a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpPut("Settings")]
    public ActionResult<SearchdomainUpdateResults> UpdateSettings([Required]string searchdomain, [Required][FromBody] SearchdomainSettings request)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        Dictionary<string, dynamic> parameters = new()
        {
            {"settings", JsonSerializer.Serialize(request)},
            {"id", searchdomain_.id}
        };
        searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters);
        searchdomain_.settings = request;
        return Ok(new SearchdomainUpdateResults(){Success = true});
    }

    /// <summary>
    /// Get the query cache size of a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpGet("QueryCache/Size")]
    public ActionResult<SearchdomainSearchCacheSizeResults> GetSearchCacheSize([Required]string searchdomain)
    {
        if (!SearchdomainHelper.IsSearchdomainLoaded(_domainManager, searchdomain))
        {
            return Ok(new SearchdomainSearchCacheSizeResults() { QueryCacheSizeBytes = 0, Success = true });
        }
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        return Ok(new SearchdomainSearchCacheSizeResults() { QueryCacheSizeBytes = searchdomain_.GetSearchCacheSize(), Success = true });
    }

    /// <summary>
    /// Clear the query cache of a searchdomain
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpPost("QueryCache/Clear")]
    public ActionResult<SearchdomainInvalidateCacheResults> InvalidateSearchCache([Required]string searchdomain)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        searchdomain_.InvalidateSearchCache();
        return Ok(new SearchdomainInvalidateCacheResults(){Success = true});
    }

    /// <summary>
    /// Get the disk size of a searchdomain in bytes
    /// </summary>
    /// <param name="searchdomain">Name of the searchdomain</param>
    [HttpGet("Database/Size")]
    public ActionResult<SearchdomainGetDatabaseSizeResult> GetDatabaseSize([Required]string searchdomain)
    {
        (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
        if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
        long sizeInBytes = DatabaseHelper.GetSearchdomainDatabaseSize(searchdomain_.helper, searchdomain);
        return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = sizeInBytes, Success = true });        
    }
}
