using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Authentication.Attributes;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Logging;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;

namespace adas_core.Controllers;

/// <summary>
/// Represents an API controller that handles HTTP requests for configuration observations at the <c>config-observations</c> route.
/// </summary>
/// <remarks>
/// Annotated with <see cref="Microsoft.AspNetCore.Mvc.ApiControllerAttribute"/> and inherits from <see cref="Microsoft.AspNetCore.Mvc.ControllerBase"/>.
/// </remarks>
/// <!-- aidoc:v1 sig=c80a66f -->
[Route("config-observations")]
[ApiController]
public class ConfigObservationController : ControllerBase
{
    private readonly IConfigObservationService _configObservationService;
    private readonly IHistoricalConfigChangesService _historicalConfigChangesService;

    /// <summary>
    /// Initializes a new instance of the <see cref="ConfigObservationController"/> class, establishing a trace identifier via <see cref="LogExecutionContext.TrySetTraceIdentifier"/> and storing the injected dependencies used for observing configuration state and retrieving historical configuration changes.
    /// </summary>
    /// <param name="configObservationService">The <see cref="IConfigObservationService"/> dependency used by the controller to observe configuration state.</param>
    /// <param name="historicalConfigChangesService">The <see cref="IHistoricalConfigChangesService"/> dependency used by the controller to retrieve historical configuration changes.</param>
    /// <!-- aidoc:v1 sig=4b3f971 body=f3e40ff -->
    public ConfigObservationController(IConfigObservationService configObservationService,
        IHistoricalConfigChangesService historicalConfigChangesService)
    {
        LogExecutionContext.TrySetTraceIdentifier(Guid.NewGuid().ToString(), true);

        _configObservationService = configObservationService;
        _historicalConfigChangesService = historicalConfigChangesService;
    }

    /// <summary>
    /// Retrieves all active configuration observations. Throws a not-found exception when no configurations are available.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing the active configuration observations.</returns>
    /// <exception cref="NotFoundException">Thrown when no configuration observations are returned by the service.</exception>
    /// <!-- aidoc:v1 sig=8c7780e body=43c1aa3 -->
    [HttpGet]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetActiveConfigObservation()
    {
        var configObservation = await _configObservationService.GetAllConfigs() ??
                                throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        return Ok(configObservation);
    }

    /// <summary>
    ///     GetByCodeSysAndCode compact configs
    /// </summary>
    /// <returns></returns>
    /// <!-- aidoc-review:v1 severity=high kind=wrong_summary
    ///      "Summary says 'GetByCodeSysAndCode compact configs' but the method is GetAllCompact, which retrieves all compact configs without filtering by code/sys." -->
    /// <!-- aidoc-review:v1 severity=high kind=missing_returns
    ///      "The <returns> tag is empty and does not describe the IActionResult containing configObservation data." -->
    [HttpGet("compact")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetAllCompact()
    {
        var configObservation = await _configObservationService.GetAllCompact();
        return Ok(configObservation);
    }


    /// <summary>
    ///     Get config by id
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    /// <!-- aidoc-review:v1 severity=medium kind=missing_exception
    ///      "Method throws InvalidFormatException when id is not a valid ObjectId, but no exception is documented" -->
    /// <!-- aidoc-review:v1 severity=medium kind=missing_exception
    ///      "Method throws NotFoundException when the config observation is not found, but no exception is documented" -->
    /// <!-- aidoc-review:v1 severity=medium kind=stale_summary
    ///      "Summary says 'Get config by id' but the method retrieves a config observation; consider 'Get config observation by id'" -->
    [HttpGet("{id}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> FindConfigObservationById(string id)
    {
        if (!ObjectId.TryParse(id, out var objectId))
            throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var configObservation = await _configObservationService.GetConfigById(objectId) ??
                                throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        return Ok(configObservation);
    }

    #region ADMPanel

    /// <summary>
    ///     Get a list of paginated items
    /// </summary>
    /// <returns></returns>
    /// <!-- aidoc-review:v1 severity=medium kind=missing_param
    ///      "The 'filter' parameter of type PaginationFilter is not documented with a <param> tag." -->
    [HttpPost("paginated-items")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetPaginatedItems([FromBody] PaginationFilter filter)
    {
        var configObservation = await _configObservationService.GetPaginatedItems(filter);

        return Ok(configObservation);
    }


    /// <summary>
    ///     Post new config observation
    /// </summary>
    /// <param name="configObservation"></param>
    /// <returns></returns>
    /// <!-- aidoc:v1 sig=f157e31 body=ba2ec54 -->
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> CreateConfigObservation([FromBody] ConfigObservation configObservation)
    {
        var newConfig = await _configObservationService.CreateConfig(configObservation);
        if (newConfig == null)
            return Conflict($"config with id: {configObservation.Id} already exists");

        await LogHistoricalConfigChanged(configObservation, null);

        return Ok(newConfig);
    }

    /// <summary>
    ///     Post new observation
    /// </summary>
    /// <param name="configObservationItem"></param>
    /// <returns></returns>
    /// <!-- aidoc-review:v1 severity=medium kind=missing_exception
    ///      "The method throws ConflictException (HttpEnum.ErrorMessage.ConflictCreationFailed) when CreateConfig returns null, but it is not documented." -->
    [HttpPost("item")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> CreateObservation([FromBody] ConfigObservation configObservationItem)
    {
        var newObservation = await _configObservationService.CreateConfig(configObservationItem)
                             ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);

        return Ok(newObservation);
    }



    /// <summary>
    ///     Update config observation item
    /// </summary>
    /// <param name="configId"></param>
    /// <param name="configObservationItem"></param>
    /// <returns></returns>
    /// <!-- aidoc-review:v1 severity=medium kind=missing_exception
    ///      "BadRequestException is thrown when configId is not a valid ObjectId, but is not documented" -->
    /// <!-- aidoc-review:v1 severity=medium kind=missing_exception
    ///      "NotFoundException is thrown when the config observation is not found, but is not documented" -->
    [HttpPut("{configId}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> UpdateConfigObservationItem(string configId,
        [FromBody] ConfigObservation configObservationItem)
    {
        if (!ObjectId.TryParse(configId, out var objectId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);

        var oldConfigObservation = await _configObservationService.GetConfigById(objectId) ??
                                   throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        var configObservation = await _configObservationService.UpdateConfig(configObservationItem);
        await LogHistoricalConfigChanged(configObservation, oldConfigObservation);
        return Ok(true);
    }

    /// <summary>
    ///     Get by name config observation item list
    /// </summary>
    /// <param name="itemName"></param>
    /// <returns></returns>
    /// <!-- aidoc:v1 sig=e7c5b3a body=ba39e44 -->
    [HttpGet("itemName/{itemName}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetConfigObservationItemByName(string itemName)
    {
        var oldConfigObservation = await _configObservationService.GetConfigObservationItemsByName(itemName);
        return Ok(oldConfigObservation);
    }

    /// <summary>
    ///     GetByCodeSysAndCode config observation item
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    /// <!-- aidoc:v1 sig=9df5989 body=611b08a -->
    [HttpGet("item/by-code-sys-and-code/{data}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetSingleConfigObservationItem(string data)
    {
        var parts = data.Split('-');
        var code = parts.Length > 0 ? parts[0] : string.Empty;
        var codingSystem = parts.Length > 1 ? parts[1] : string.Empty;
        var name = parts.Length > 2 ? parts[2] : string.Empty;
        var originalName = parts.Length > 3 ? parts[3] : string.Empty;
        var item = await _configObservationService.GetSingleConfigObservationItem(code, codingSystem, name,
            originalName);
        return Ok(item);
    }


    /// <summary>
    /// Deletes a single configuration observation item.
    /// </summary>
    /// <param name="configObservationItem">The configuration observation item to delete, provided in the request body.</param>
    /// <returns>An <see cref="IActionResult"/> containing the result of the deletion operation.</returns>
    /// <exception cref="BadRequestException">Thrown when the <paramref name="configObservationItem"/> is null, indicating missing required parameters.</exception>
    /// <!-- aidoc:v1 sig=f6b0600 body=8526bee -->
    [HttpDelete("delete/item")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> DeleteSingleConfigObservationItem(
            [FromBody] ConfigObservation configObservationItem)
    {
        if (configObservationItem == null)
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
        var item = await _configObservationService.DeleteSingleConfigObservationItem(configObservationItem);
        return Ok(item);
    }

    /// <summary>
    ///     GetByCodeSysAndCode config all observation items by name
    /// </summary>
    /// <param name="itemName"></param>
    /// <returns></returns>
    /// <!-- aidoc-review:v1 severity=high kind=wrong_summary
    ///      "Summary references 'GetByCodeSysAndCode' and mentions no CodeSys/Code parameters exist; the method is GetConfigObservationItemsByName with only an itemName parameter." -->
    /// <!-- aidoc-review:v1 severity=high kind=missing_exception
    ///      "Method throws BadRequestException when itemName.Length < 2, but no <exception> tag documents this." -->
    /// <!-- aidoc-review:v1 severity=high kind=missing_returns
    ///      "<returns> tag is empty; the method returns IActionResult (Ok with config observation items)." -->
    [HttpGet("{itemName}/name")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetConfigObservationItemsByName(string itemName)
    {
        if (itemName.Length < 2) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestIncorrectLength);
        var items = await _configObservationService.GetConfigObservationItemsByName(itemName);
        return Ok(items);
    }

    /// <summary>
    /// Retrieves the list of configuration names available for observations, restricted to users with the required role and source-level display permission.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing the configuration names returned by the observation service wrapped in an HTTP 200 OK response.</returns>
    /// <!-- aidoc:v1 sig=acfc5f7 body=cd591f9 -->
    [HttpGet("get-name-obs")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetConfigNames()
    {
        var items = await _configObservationService.GetConfigNames();
        return Ok(items);
    }

    /// <summary>
    /// Logs a historical configuration change for an observation configuration, capturing the user identity (falling back to the remote IP address when the user is not authenticated) along with the new and previous configuration states serialized as JSON.
    /// </summary>
    /// <param name="newConfigObservation">The new configuration observation to log, or <c>null</c> to indicate the configuration was removed.</param>
    /// <param name="oldConfigObservation">The previous configuration observation to log, or <c>null</c> to indicate no prior configuration existed.</param>
    /// <!-- aidoc:v1 sig=fcb81f0 body=ac21cc9 -->
    private async Task LogHistoricalConfigChanged(ConfigObservation? newConfigObservation,
            ConfigObservation? oldConfigObservation)
    {
        var user = HttpContext.User.Identity?.Name ?? HttpContext.Connection.RemoteIpAddress?.ToString();
        await _historicalConfigChangesService.LogConfigChanged(user!, DisplayConfigEnums.ConfigTypes.ObservationCfg,
            newConfigObservation?.ToJson() ?? string.Empty, oldConfigObservation?.ToJson() ?? string.Empty);
    }

    #endregion
}