using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Models;
using Timeline.Models.Validation;
using Timeline.Services.Exceptions;
namespace Timeline.Services
{
    /// 
    /// This service provide some basic timeline functions, which should be used internally for other services.
    /// 
    public interface IBasicTimelineService
    {
        /// 
        /// Check whether a timeline with given id exists without getting full info.
        /// 
        /// The timeline id.
        /// True if exist. Otherwise false.
        Task CheckExistence(long id);
        /// 
        /// Get the timeline id by name.
        /// 
        /// Timeline name.
        /// Id of the timeline.
        /// Thrown when  is null.
        /// Throw when  is of bad format.
        /// 
        /// Thrown when timeline with name  does not exist.
        /// If it is a personal timeline, then inner exception is .
        /// 
        /// 
        /// If name is of personal timeline and the timeline does not exist, it will be created if user exists.
        /// If the user does not exist,   will be thrown with  as inner exception.
        ///
        Task GetTimelineIdByName(string timelineName);
    }
    public class BasicTimelineService : IBasicTimelineService
    {
        private readonly DatabaseContext _database;
        private readonly IBasicUserService _basicUserService;
        private readonly IClock _clock;
        private readonly GeneralTimelineNameValidator _generalTimelineNameValidator = new GeneralTimelineNameValidator();
        public BasicTimelineService(DatabaseContext database, IBasicUserService basicUserService, IClock clock)
        {
            _database = database;
            _basicUserService = basicUserService;
            _clock = clock;
        }
        protected TimelineEntity CreateNewTimelineEntity(string? name, long ownerId)
        {
            var currentTime = _clock.GetCurrentTime();
            return new TimelineEntity
            {
                Name = name,
                NameLastModified = currentTime,
                OwnerId = ownerId,
                Visibility = TimelineVisibility.Register,
                CreateTime = currentTime,
                LastModified = currentTime,
                CurrentPostLocalId = 0,
                Members = new List()
            };
        }
        public async Task CheckExistence(long id)
        {
            return await _database.Timelines.AnyAsync(t => t.Id == id);
        }
        public async Task GetTimelineIdByName(string timelineName)
        {
            if (timelineName == null)
                throw new ArgumentNullException(nameof(timelineName));
            if (!_generalTimelineNameValidator.Validate(timelineName, out var message))
                throw new ArgumentException(message);
            timelineName = TimelineHelper.ExtractTimelineName(timelineName, out var isPersonal);
            if (isPersonal)
            {
                long userId;
                try
                {
                    userId = await _basicUserService.GetUserIdByUsername(timelineName);
                }
                catch (UserNotExistException e)
                {
                    throw new TimelineNotExistException(timelineName, e);
                }
                var timelineEntity = await _database.Timelines.Where(t => t.OwnerId == userId && t.Name == null).Select(t => new { t.Id }).SingleOrDefaultAsync();
                if (timelineEntity != null)
                {
                    return timelineEntity.Id;
                }
                else
                {
                    var newTimelineEntity = CreateNewTimelineEntity(null, userId);
                    _database.Timelines.Add(newTimelineEntity);
                    await _database.SaveChangesAsync();
                    return newTimelineEntity.Id;
                }
            }
            else
            {
                var timelineEntity = await _database.Timelines.Where(t => t.Name == timelineName).Select(t => new { t.Id }).SingleOrDefaultAsync();
                if (timelineEntity == null)
                {
                    throw new TimelineNotExistException(timelineName);
                }
                else
                {
                    return timelineEntity.Id;
                }
            }
        }
    }
}