using PhilExampleCrawler.Common.Models;
using PhilExampleCrawler.DataBase;
using PhilExampleCrawler.DataBase.Models;

namespace WebAPI
{
    internal static class Transfrom
    {
        internal static User? ToCommon(this DB_User? dbUser)
            => dbUser == null 
                              ? null
                              : new(dbUser.ID, dbUser.AuthCode, dbUser.Phone, dbUser.Optin_Telegram, dbUser.CreateDate)
                              {
                                  HasTelegram = dbUser.HasTelegram,
                                  CrawlSessions = dbUser.CrawlSessions.Select(x => x.ToCommon()).ToList()
                              };

        internal static CrawlSession? ToCommon(this DB_CrawlSession? dbCS)
            => dbCS == null
                            ? null
                            : new(id: dbCS.ID,
                                  searchParams: new CrawlSearchParams(dbCS.Keywords,
                                                                      dbCS.LocationText,
                                                                      dbCS.CategoryID,
                                                                      dbCS.RadiusKM),
                                minPrice: dbCS.MinPrice,
                                maxPrice: dbCS.MaxPrice,
                                isPrivate: dbCS.IsPrivate,
                                isCommercial: dbCS.IsCommercial);

        internal static Insertion? ToCommon(this DB_Insertion? dbIns)
            => dbIns == null
                             ? null
                             : new Insertion(href: dbIns.Href,
                                             crawlSessionID: dbIns.CrawlSessionID,
                                             name: dbIns.Name,
                                             postCode: dbIns.PostCode,
                                             locationStr: dbIns.LocationStr,
                                             price: dbIns.Price,
                                             is_vb: dbIns.Is_VB,
                                             date: dbIns.Date,
                                             isTopAd: dbIns.IsTopAd,
                                             isHighlight: dbIns.IsHighlight,
                                             isRequest: dbIns.IsRequest);

        internal static DB_Insertion To_DB(this Insertion ins)
        {
            return new DB_Insertion(href: ins.Href,
                                    crawlSessionID: ins.CrawlSessionID,
                                    name: ins.Name,
                                    postCode: ins.PostCode,
                                    locationStr: ins.LocationStr,
                                    price: ins.Price,
                                    is_vb: ins.Is_VB,
                                    date: ins.Date,
                                    isTopAd: ins.IsTopAd,
                                    isHighlight: ins.IsHighlight,
                                    isRequest: ins.IsRequest);
        }

        internal static DB_CrawlSession ToDB(this CrawlSession cs, int userID)
                => new(cs.ID,
                       userID,
                       cs.SearchParams.KeyWords,
                       cs.SearchParams.LocationStr,
                       cs.SearchParams.CategoryID,
                       cs.SearchParams.Radius,
                       cs.MinPrice,
                       cs.MaxPrice,
                       cs.IsPrivate,
                       cs.IsCommercial);
    }

    internal static class ThreadSafeCache
    {
        private static readonly List<User> _userCache = new();
        private static readonly object _userLock = new();

        internal static async Task<User?> GetUserAsync(int userID)
        {
            User? user = null;
            lock (_userLock)
                user = _userCache.FirstOrDefault(u => u.ID == userID);

            //get from db and add to cache
            if (user == null)
            {
                UserAccess userDB = new(new DBSettings());
                DB_User? dbUser = await userDB.GetUserAsync(userID);
                user = dbUser.ToCommon();

                if (user != null)
                    lock (_userLock)
                        _userCache.Add(user);
            }

            return user;
        }

        internal static async Task<User?> AddUserAsync(string authCode, string phoneNumber, bool optin_telegram)
        {
            UserAccess userDB = new(new DBSettings());
            DB_User? dbUser = await userDB.AddUserAsync(authCode, phoneNumber, optin_telegram);
            User? user = dbUser.ToCommon();

            if (user != null)
                lock (_userLock)
                    _userCache.Add(user);

            return user;
        }

        internal static async Task<CrawlSession?> AddCrawlSessionAsync(int userID, CrawlSession cs)
        {
            CrawlSession? sess = null;
            User? user = await GetUserAsync(userID);
            if (user != null)
            {
                CrawlSessionAccess csDB = new(new DBSettings());
                DB_CrawlSession? addedSess = await csDB.AddCrawlSessionAsync(cs.ToDB(userID));

                sess = addedSess.ToCommon();
                if (sess != null)
                    lock (_userLock)
                        user.CrawlSessions.Add(sess);
            }
            return sess;
        }

        internal static async Task<bool> UpdateCrawlSessionAsync(int userID, CrawlSession cs)
        {
            CrawlSessionAccess csDB = new(new DBSettings());
            bool updated = await csDB.UpdateCrawlSessionAsync(cs.ToDB(userID));

            if (updated)
            {
                //Replace in cache
                User? cachedUser = null;
                lock (_userLock)
                {
                    cachedUser = _userCache.FirstOrDefault(u => u.CrawlSessions.Any(cs => cs.ID == cs.ID));
                    if (cachedUser != null)
                    {
                        int oldItemIndex = cachedUser.CrawlSessions.FindIndex(x => x.ID == cs.ID);
                        if (oldItemIndex != -1)
                            cachedUser.CrawlSessions[oldItemIndex] = cs;
                    }
                }
            }

            return updated;
        }

        internal static async Task<bool> RemoveCrawlSessionAsync(int crawlSessionID)
        {
            CrawlSessionAccess csDB = new(new DBSettings());
            bool deleted = await csDB.RemoveCrawlSessionAsync(crawlSessionID);

            if (deleted)
            {
                User? cachedUser = null;
                lock (_userLock)
                {
                    cachedUser = _userCache.FirstOrDefault(u => u.CrawlSessions.Any(cs => cs.ID == crawlSessionID));
                    if (cachedUser != null)
                        cachedUser.CrawlSessions.RemoveAll(cs => cs.ID == crawlSessionID);
                }
            }

            return deleted;
        }

        internal static async Task<List<Insertion>> AddInsertionsAsync(List<Insertion> insertions)
        {
            CrawlSessionAccess csDB = new(new());
            List<DB_Insertion> dbIns = await csDB.AddInsertionsAsync(insertions.Select(x => x.To_DB()));
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
            return dbIns.Select(x => x.ToCommon()).ToList();
#pragma warning restore CS8619
        }

        internal static async Task<List<Insertion>> GetInsertions(int userID, int? limit)
        {
            CrawlSessionAccess csDB = new(new());
            List<DB_Insertion> dbIns = await csDB.GetInsertions(userID, limit);
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
            return dbIns.Select(x => x.ToCommon()).ToList();
#pragma warning restore CS8619
        }
    }
}
