using DataManagers;
using DbConnectionLibrairie;
using DTOs;
using ManagerInterfaces;
using Microsoft.AspNetCore.Mvc;
using ServiceManagers;
using Microsoft.Extensions.Logging;
using OrderCriterias;
using Microsoft.AspNetCore.Http.HttpResults;

namespace WebApi.Controllers
{
    [ApiController]
    [Route("api/v[version]/")]
    public class AdministratorController : ControllerBase
    {
        private IAdministratorManager<AdministratorDto> mgr;

        private readonly Logger<AdministratorController> logger;

        public AdministratorController(MyDbContext dbContext, Logger<AdministratorController> logger)
        {
            mgr = new AdministratorServiceManager(dbContext);
            this.logger = logger;
        }

        /// <summary>
        /// add an administrator
        /// </summary>
        /// <param name="administrator">the administrator to add</param>
        /// <returns>
        /// status code :
        /// 200 if the administrator is added
        /// 202 if the administrator is added
        /// 208 if the administrator was already in the database
        /// 500 if there was an internal error that doesn't allow to continue
        /// return content :
        /// no content when status code = 200
        /// administrator added when status code = 202
        /// administrator already in the database that equals the one we wanted to add when status code = 208
        /// no content when status code = 500
        /// </returns>
        public async Task<IActionResult> PostAdministrator(AdministratorDto administrator)
        {
            int count = mgr.getNbElements(); // count : number of elements before opperation
            var tmp = await mgr.addAdmin(administrator); //  tmp : administrator recieve by the addAdmin method
            if (mgr.getNbElements() == count) // <=> not added
            {
                if(tmp.Username == administrator.Username)
                {
                    // it was already in the database
                    logger.LogInformation(message: $"want to add an administrator already in the database : {tmp.ToString()}");
                    return StatusCode(208, tmp);
                }
                else
                {
                    // we recieve an administrator already in the database
                    // that should be equal to the administrator that we
                    // wanted to add but it isn't
                    logger.LogCritical(message: "controller's add fail (already in the database) but\nadministrator" +
                        $" recieve isn't the same as the one we wanted to add.\n" +
                        $"Administrator recieve : {tmp.ToString()}\nadministrator to add : {administrator.ToString()}");
                    return StatusCode(500);
                }
            }
            // added
            if (tmp.Username == administrator.Username)
            {
                logger.LogTrace(message: $"administrator added : {tmp.ToString()}");
                // the administrator has been added and we recieved him
                return StatusCode(202, tmp);
            }
            else
            {
                // the administrator may be added but we do not recieved him
                if (mgr.getAdministratorByUsername(administrator.Username) != null)
                {
                    // he is added
                    logger.LogError(message: $"administrator added but not recieved");
                    return Ok(); // not 202 to make a difference between 2 cases
                }
                else
                {
                    // he is not added
                    logger.LogCritical(message: "administrator that we wanted to add not added\nand we have added another one");
                    if (mgr.getAdministratorByUsername(administrator.Username) == null) // <=> not added
                    return StatusCode(500);
                }
            }
            logger.LogError(message: "this case should not append");
            return StatusCode(500);
        }

        /// <summary>
        /// get a part of all administrators
        /// </summary>
        /// <param name="page">the actual page</param>
        /// <param name="count">number of T element in a page</param>
        /// <param name="orderCriteria">the order criteria</param>
        /// <returns>
        /// status code :
        /// 200 if we got a set
        /// 204 if we got an empty set
        /// 400 if we got nothing
        /// 
        /// return content :
        /// a tuple of the number of page and
        /// all T element in the database for
        /// the page nb if status code = 200
        /// the number of page for count element if status code = 204
        /// nothing if status code = 400
        /// </returns>
        public async Task<IActionResult> GetSomeAdministrators(int page, int count = 10, AdministratorOrderCriteria orderCriteria = AdministratorOrderCriteria.ById)
        {
            var tmp = await mgr.getAdministrators(page, count, orderCriteria);
            if (tmp.administrators == null)
            {
                logger.LogInformation(message: "get admin : bad request (page or/and count incorrect)");
                return BadRequest(tmp.nbPages);
            }
            else if (tmp.administrators.Count() == 0)
            {
                logger.LogWarning(message: $"get admin : no content. number of element : {mgr.getNbElements()}, page wanted : {page}, number of elements in a page : {count}");
                return NoContent();
            }
            else
            {
                logger.LogTrace(message: $"get admins : page = {page}, count = {count}, order criteria = {orderCriteria switch
                {
                    AdministratorOrderCriteria.ById => "byId",
                    AdministratorOrderCriteria.ByUserName => "byUsername",
                    _ => "none"
                }}");
                return Ok(tmp);
            }
        }

        /// <summary>
        /// delete an administrator
        /// </summary>
        /// <param name="id">the id of the administrator to delete</param>
        /// <returns>
        /// <returns>
        /// status code :
        /// 200 if the administrator is removed
        /// 204 if the administrator is removed
        /// 400 if the id is incorrect
        /// 500 if there was an internal error that doesn't allow to continue
        /// return content :
        /// aministrator deleted when status code = 200
        /// no content when status code = 204
        /// no content when status code = 400
        /// no content when status code = 500
        /// </returns>
        public async Task<IActionResult> DeleteAdministrator(int id)
        {
            if (id < 0)
            {
                logger.LogError("want to delete an administrator with an id less than 0");
                return BadRequest();
            }
            int count = mgr.getNbElements(); // count : number of elements before opperation
            var tmp = await mgr.removeAdmin(id);
            if (tmp == null) // we don't recieve the administrator
            {
                if(mgr.getNbElements() == count) // he is not deleted 
                {
                    if (mgr.getAdministrator(id) != null)
                    {
                        logger.LogCritical(message: "remove administrator fail : administrator not removed and not recieved");
                        return StatusCode(500);
                    }
                    else
                    {
                        logger.LogInformation(message: "trying to remove an administrator with an id who don't exist");
                        return BadRequest();
                    }
                }
                else // he may be deleted
                {
                    if (mgr.getAdministrator(id) == null) // he must be deleted
                    {
                        logger.LogError(message: "administrator removed but not returned");
                        return NoContent();
                    }
                    else // he is not deleted
                    {
                        logger.LogCritical(message: "remove administrator fail : administrator to remove not remove\ninstead, anotherone is deleted and we recieved nothing");
                        return StatusCode(500);
                    }
                }
            }
            if(mgr.getNbElements() == count)
            {
                // <=> we have recieved an administrator which should be deleted
                // but since we have the same number of administrator than
                // before deletion, it isn't deleted
                logger.LogCritical(message: $"administrator \"{tmp.ToString()}\"should be delete but it isn't");
                return StatusCode(500);
            }
            logger.LogTrace(message: $"administrator removed {tmp.ToString()}");
            return Ok(tmp);
        }

        /// <summary>
        /// update an administrator
        /// </summary>
        /// <param name="id">id of the administrator to update</param>
        /// <param name="administrator">an administrator who contains all properties to change</param>
        /// <returns>
        /// status code :
        /// 200 if the administrator is modified
        /// 204 if we wanted to modify an administrator who don't exist
        /// 208 if the new username of the administrator is already used
        /// 500 if there was an internal error that doesn't allow to continue
        /// 
        /// return content :
        /// the administrator modified when status code = 200
        /// nothing when status code = 204
        /// the administrator that already have the username when status code = 208
        /// nothing when status code = 500
        /// </returns>
        public async Task<IActionResult> PutAdministrator(int id, AdministratorDto administrator)
        {
            var tmp = await mgr.getAdministratorByUsername(administrator.Username);
            if (tmp != null)
            {
                logger.LogTrace(message: "Want to modify into an administrator who already exist");
                return StatusCode(StatusCodes.Status208AlreadyReported, tmp);
            }
            tmp = await mgr.updateAdministrator(id, administrator);
            if(tmp == null)
            {
                tmp = await mgr.getAdministrator(id);
                if (tmp == null) // Ok, it don't exist
                {
                    logger.LogWarning(message: "Want to modify an administrator who don't exist");
                    return NoContent();
                }
                else
                {
                    if (tmp.Username == administrator.Username && tmp.HashedPassword == administrator.HashedPassword)
                    {
                        logger.LogError(message: "Administrator changed but not recieved");
                        return Ok(tmp);
                    }
                    else
                    {
                        logger.LogCritical(message: "administrator haven't changed and we recieved nothing");
                        return StatusCode(500);
                    }
                }
            }
            else if(tmp.Id == id) // we recieve him
            {
                if(tmp.HashedPassword == administrator.HashedPassword
                    && tmp.Username == administrator.Username) // he is changed
                {
                    logger.LogTrace(message: $"administrator with id {id} modified in {administrator.ToString()}");
                    return Ok(tmp);
                }
                else // he haven't changed
                {
                    var tmp2 = (await mgr.getAdministratorByUsername(administrator.Username));
                    if(tmp2 != null) // it change another administrator
                        logger.LogCritical(message: "administrator should have changed but he haven't. Instead, another one changed");
                    else // nothing have changed
                        logger.LogCritical(message: "administrator should have changed but he haven't and we recieved him");
                    return StatusCode(500);
                }
            }
            else // we recieve another one
            {
                var tmp2 = await mgr.getAdministrator(id);
                if (tmp2 == null)
                { // ok, he d'ont exist.
                    logger.LogError(message: "Want to modify an administrator who don't exist but recieved a random one");
                    return NoContent();
                }
                else if (tmp2.Username == administrator.Username && tmp2.HashedPassword == administrator.HashedPassword)
                {
                    logger.LogError(message: $"administrator modified but recieved a random one");
                    return Ok(tmp2);
                }
                else
                {
                    logger.LogCritical(message: "administrator that we wanted to modify not modified");
                    return StatusCode(500);
                }
            }
        }
    }
}
