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.MongoModels;
using adas_core.Logging;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;

namespace adas_core.Controllers;

/// <summary>
/// Provides API endpoints for managing patient admission records in the ADAS system.
/// </summary>
[Route("admissions")]
[ApiController]
public class AdmissionController : ControllerBase
{
    private readonly IAdmissionService _admissionService;
    private readonly ILogger<AdmissionController> _logger;
    private readonly IPatientService _patientService;
    private readonly IUnitService _unitService;

    /// <summary>
    /// Initializes a new instance of the <see cref="AdmissionController"/> class.
    /// </summary>
    /// <param name="logger">The logger instance used to log diagnostic information for the controller.</param>
    /// <param name="admissionService">The service responsible for handling admission-related business logic.</param>
    /// <param name="patientService">The service responsible for managing patient-related operations.</param>
    /// <param name="unitService">The service responsible for managing unit-related operations.</param>
    /// <exception cref="ArgumentNullException">
    /// Thrown when any of the parameters <paramref name="logger"/>, <paramref name="admissionService"/>,
    /// <paramref name="patientService"/>, or <paramref name="unitService"/> is <c>null</c>.
    /// </exception>
 
    public AdmissionController(ILogger<AdmissionController> logger, IAdmissionService admissionService,
        IPatientService patientService, IUnitService unitService)
    {
        LogExecutionContext.TrySetTraceIdentifier(Guid.NewGuid().ToString(), true);

        _logger = logger;
        _admissionService = admissionService;
        _patientService = patientService;
        _unitService = unitService;
    }

    /// <summary>
    /// Retrieves all admissions records.
    /// </summary>
    /// <returns>An <see cref="IActionResult"/> containing the collection of admissions with an HTTP 200 OK response.</returns>
    [HttpGet]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetAllAdmissions()
    {
        var admissions = await _admissionService.GetAdmissionsAsync();

        return Ok(admissions);
    }

    /// <summary>
    /// Retrieves an admission by its identifier, validating the ID format and returning the admission record if it exists.
    /// </summary>
    /// <param name="id">The string identifier of the admission to retrieve.</param>
    /// <returns>An <see cref="IActionResult"/> containing the admission details when found.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> is not a valid ObjectId format.</exception>
    /// <exception cref="NotFoundException">Thrown when no admission matching the provided <paramref name="id"/> is found.</exception>
    [HttpGet("{id}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> GetAdmissionbyId(string id)
    {
        if (!ObjectId.TryParse(id, out var admissionId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var admission = await _admissionService.GetAdmissionByIdAsync(admissionId);
        if (admission != null) return Ok(admission);

        _logger.LogDebug("Admission id: {id} not Found", id);

        throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
    }

    /// <summary>
    /// Creates a new admission record by validating the input and inserting it via the admission service.
    /// Throws a bad request exception if the admission payload is missing or the required NHC identifier is empty.
    /// </summary>
    /// <param name="admission">The admission data to create, provided in the request body.</param>
    /// <returns>An <see cref="IActionResult"/> containing the newly created admission wrapped in an OK response.</returns>
    /// <exception cref="BadRequestException">Thrown when <paramref name="admission"/> is null or when its NHC field is null or empty.</exception>
    [HttpPost]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> CreateNewAdmission([FromBody] Admission admission)
    {
        if (admission == null || string.IsNullOrEmpty(admission.Nhc))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var newAdmission = await _admissionService.InsertAdmission(admission);
        return Ok(newAdmission);
    }

    /// <summary>
    /// Updates an existing admission record using the provided data, after validating the NHC identifier.
    /// </summary>
    /// <param name="admission">The admission entity containing the updated information to persist.</param>
    /// <returns>An <see cref="IActionResult"/> indicating the outcome of the update operation.</returns>
    /// <exception cref="BadRequestException">Thrown when the admission's NHC is null or empty.</exception>
    [HttpPut]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> UpdateAdmission([FromBody] Admission admission)
    {
        if (string.IsNullOrEmpty(admission.Nhc))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        await _admissionService.UpdateAdmissionAsync(admission);
        return Ok();
    }

    /// <summary>
    /// Deletes an admission record identified by the specified id, after validating the id format and confirming the resource exists.
    /// </summary>
    /// <param name="id">The string identifier of the admission to delete; expected to be a valid ObjectId.</param>
    /// <returns>An <see cref="IActionResult"/> that returns OK when the admission is successfully deleted.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> cannot be parsed as a valid ObjectId.</exception>
    /// <exception cref="NotFoundException">Thrown when no admission is found for the specified id.</exception>
    [HttpDelete("{id}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> DeleteAdmissionById(string id)
    {
        if (!ObjectId.TryParse(id, out var admissionId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var admission = await _admissionService.GetAdmissionByIdAsync(admissionId);
        if (admission == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
        await _admissionService.DeleteAdmissionAsync(admission);
        return Ok();
    }

    /// <summary>
    /// Admits a patient identified by the admission identifier, ensuring the admission exists, belongs to a unit with manual admit enabled, and that a matching patient record is available.
    /// </summary>
    /// <param name="id">The string identifier of the admission; must be a valid ObjectId.</param>
    /// <returns>An <see cref="IActionResult"/> containing the admitted patient when the operation succeeds.</returns>
    /// <exception cref="BadRequestException">Thrown when <paramref name="id"/> is not a valid ObjectId format.</exception>
    /// <exception cref="NotFoundException">Thrown when no admission exists for the given id, or when the admission's unit configuration cannot be found.</exception>
    /// <exception cref="ConflictException">Thrown when the unit's configuration has manual admit disabled, or when no patient record matches the admission's patient number.</exception>
    [HttpPost("{id}/admission")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> AdmitPatient(string id)
    {
        if (!ObjectId.TryParse(id, out var admissionId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var admission = await _admissionService.GetAdmissionByIdAsync(admissionId);
        if (admission == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);


        var unitConfig = await _unitService.FindById(admission.UnitId);
        switch (unitConfig)
        {
            case null:
                throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
            case { Configuration.ManualAdmit: false }:
                throw new ConflictException(HttpEnum.ErrorMessage.ServiceUnavailableCensusInAutomaticMode);
        }

        await _admissionService.AdmitPatient(admission);
        var patient = await _patientService.FindByPatientNumber(admission.Nhc) ??
                      throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted);
        return Ok(patient);
    }

    /// <summary>
    /// Admits a new patient by updating the admission record and registering the patient in the specified unit, provided the unit is configured for manual admission.
    /// </summary>
    /// <param name="admission">The admission details, including the target unit identifier and patient health number (NHC), used to update the admission and locate the patient.</param>
    /// <returns>An <see cref="IActionResult"/> containing the admitted patient information when the operation succeeds.</returns>
    /// <exception cref="BadRequestException">Thrown when the <paramref name="admission"/> parameter is null.</exception>
    /// <exception cref="NotFoundException">Thrown when no unit configuration is found for the specified unit identifier.</exception>
    /// <exception cref="ConflictException">Thrown when the target unit operates in automatic census mode, or when the patient cannot be retrieved after the admission is registered.</exception>
    [HttpPost("admit/new-patient")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> AdmitNewPatient(Admission admission)
    {
        if (admission == null) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
        await _admissionService.UpdateAdmissionAsync(admission);

        var unitConfig = await _unitService.FindById(admission.UnitId);
        switch (unitConfig)
        {
            case null:
                throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
            case { Configuration.ManualAdmit: false }:
                throw new ConflictException(HttpEnum.ErrorMessage.ServiceUnavailableCensusInAutomaticMode);
        }

        await _admissionService.AdmitPatient(admission);
        var patient = await _patientService.FindByPatientNumber(admission.Nhc) ??
                      throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted);
        return Ok(patient);
    }

    /// <summary>
    /// Returns a patient to the admissions list by processing the patient-to-admission transition for the given patient id.
    /// Validates the id format, resolves the patient's unit configuration, and blocks the operation when the unit operates in automatic (non-manual-move) mode.
    /// </summary>
    /// <param name="id">The string representation of the patient's identifier used to locate the patient record.</param>
    /// <returns>An <see cref="IActionResult"/> representing an HTTP 200 OK response when the patient is successfully returned to admissions.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> is not a valid identifier format.</exception>
    /// <exception cref="NotFoundException">Thrown when no unit configuration is found for the patient's unit.</exception>
    /// <exception cref="ConflictException">Thrown when the unit's configuration has manual move disabled, indicating the census is in automatic mode.</exception>
    [HttpPost("{id}/patient-to-admission")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> ReturnPatientToAdmission(string id)
    {
        if (!ObjectId.TryParse(id, out var patientId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var patient = await _patientService.FindById(patientId);
        var unitConfig = await _unitService.FindById(patient!.UnitId);
        switch (unitConfig)
        {
            case null:
                throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
            case { Configuration.ManualMove: false }:
                throw new ConflictException(HttpEnum.ErrorMessage.ServiceUnavailableCensusInAutomaticMode);
            default:
                await _admissionService.ReturnPatientToAdmissions(patient.Id);
                return Ok();
        }
    }

    /// <summary>
    /// Handles the process of returning a patient from a temporal bed back to their admission record.
    /// Validates the provided patient identifier format and delegates the transfer operation to the admission service.
    /// </summary>
    /// <param name="admission">The admission data containing the details required to restore the patient to the admissions list.</param>
    /// <param name="id">The string representation of the patient's identifier from the route, expected to be a valid ObjectId.</param>
    /// <returns>An <see cref="IActionResult"/> that returns HTTP 200 (OK) when the patient is successfully returned to admissions.</returns>
    /// <exception cref="BadRequestException">Thrown when the provided <paramref name="id"/> is not in a valid ObjectId format.</exception>
    [HttpPost("{id}/temporal-bed")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> ReturnToAdmissionFromTemporalBed([FromBody] Admission admission, string id)
    {
        if (!ObjectId.TryParse(id, out var patientId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        var patient = await _patientService.FindById(patientId);
        await _admissionService.ReturnPatientToAdmissions(patient!.Id, admission);
        return Ok();
    }

    /// <summary>
    /// Handles a GET request to return a patient to admission by searching admission records
    /// associated with the specified patient number and unit. Validates the unit identifier
    /// format before delegating the search to the admission service.
    /// </summary>
    /// <param name="patientNumber">The unique identifier of the patient to be returned to admission.</param>
    /// <param name="unitId">The identifier of the unit used to scope the admission search; must be a valid ObjectId.</param>
    /// <returns>An <see cref="IActionResult"/> containing the matching admission records returned by the service.</returns>
    /// <exception cref="BadRequestException">Thrown when <paramref name="unitId"/> is not a valid ObjectId format.</exception>
    [HttpGet("{patientNumber}/{unitId}")]
    [AuthorizeRoles(PermissionEnum.RolesType.AuthSome)]
    [PermissionAuthorize("Source", "DisplayId")]
    public async Task<IActionResult> ReturnPatientToAdmission(string patientNumber, string unitId)
    {
        if (!ObjectId.TryParse(unitId, out var unitObjectId))
            throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
        return Ok(await _admissionService.SearchByPatientNumberAndDistinctUnit(patientNumber, unitObjectId));
    }
}