using Blahblah.FlowerApp.Data.Model;
using Microsoft.Extensions.Logging;
using SQLite;

namespace Blahblah.FlowerApp.Data;

public class FlowerDatabase : ILoggerContent
{
    public ILogger Logger { get; }

    public FlowerDatabase Database => this;

    private SQLiteAsyncConnection database = null!;

    public FlowerDatabase(ILogger<FlowerDatabase> logger)
    {
        Logger = logger;
    }

    private Dictionary<int, NamedItem>? categories;

    private Dictionary<int, EventItem>? events;

    public Dictionary<int, NamedItem>? Categories => categories;

    public string Category(int categoryId)
    {
        if (categories?.TryGetValue(categoryId, out var category) == true)
        {
            return category.Name;
        }
        return Constants.CategoryOther;
    }

    public string Event(int eventId)
    {
        if (events?.TryGetValue(eventId, out var @event) == true)
        {
            return @event.Name;
        }
        return Constants.EventUnknown;
    }

    private async Task Init()
    {
        if (database is not null)
        {
            return;
        }

#if DEBUG
        Logger.LogInformation("database path: {path}", Constants.DatabasePath);
#endif

        database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.SQLiteFlags);

#if DEBUG1
        var result =
#endif
        await database.CreateTablesAsync(CreateFlags.None,
            typeof(FlowerItem),
            typeof(RecordItem),
            typeof(PhotoItem),
            typeof(UserItem),
            typeof(DefinitionItem),
            typeof(ParamItem),
            typeof(LogItem));

#if DEBUG1
        foreach (var item in result.Results)
        {
            this.LogInformation($"create table {item.Key}, result: {item.Value}");
        }
#endif
    }

    public async Task Setup()
    {
        await Init();

#if DEBUG1
        var token = "RF4mfoUur0vHtWzHwD42ka0FhIfGaPnBxoQgrXOYEDg=";
#else
        var tk = await database.Table<ParamItem>().FirstOrDefaultAsync(p => p.Code == Constants.LastTokenName);
        var token = tk?.Value;
#endif
        if (token is string t)
        {
            Constants.SetAuthorization(t);
            var user = await database.Table<UserItem>().FirstOrDefaultAsync(u => u.Token == t);
            if (user != null)
            {
                AppResources.SetUser(user);
            }
        }

        var version = await database.Table<ParamItem>().FirstOrDefaultAsync(p => p.Code == Constants.ApiVersionName);
        var definition = await Constants.Initialize(this, version?.Value);

        if (definition != null)
        {
            categories = definition.Categories;
            events = definition.Events;

            this.LogInformation($"new version founded, from ({version?.Value}) to ({definition.ApiVersion})");

            if (version == null)
            {
                version = new ParamItem
                {
                    Code = Constants.ApiVersionName,
                    Value = definition.ApiVersion
                };
            }
            else
            {
                version.Value = definition.ApiVersion;
            }
            await database.InsertOrReplaceAsync(version);

            // replace local definitions
            await database.DeleteAllAsync<DefinitionItem>();

            var defs = new List<DefinitionItem>();
            foreach (var category in definition.Categories)
            {
                defs.Add(new DefinitionItem
                {
                    DefinitionType = 0,
                    DefinitionId = category.Key,
                    Name = category.Value.Name,
                    Description = category.Value.Description
                });
            }
            foreach (var @event in definition.Events)
            {
                defs.Add(new DefinitionItem
                {
                    DefinitionType = 1,
                    DefinitionId = @event.Key,
                    Name = @event.Value.Name,
                    Description = @event.Value.Description,
                    Unique = @event.Value.Unique
                });
            }
            var rows = await database.InsertAllAsync(defs);
            this.LogInformation($"{defs.Count} definitions, {rows} rows inserted");
        }
        else
        {
            // use local definitions
            var defs = await database.Table<DefinitionItem>().ToListAsync();
            var cates = new Dictionary<int, NamedItem>();
            var evts = new Dictionary<int, EventItem>();
            foreach (var d in defs)
            {
                if (d.DefinitionType == 0)
                {
                    // category
                    cates[d.DefinitionId] = new NamedItem(d.Name, d.Description);
                }
                else if (d.DefinitionType == 1)
                {
                    // event
                    evts[d.DefinitionId] = new EventItem(d.Name, d.Description, d.Unique ?? false);
                }
            }
            categories = cates;
            events = evts;
        }
    }

    public async Task<int> GetLogCount()
    {
        await Init();
        return await database.Table<LogItem>().Where(l => l.OwnerId < 0 || l.OwnerId == AppResources.User.Id).CountAsync();
    }

    public async Task<LogItem[]> GetLogs()
    {
        await Init();
        return await database.Table<LogItem>().Where(l => l.OwnerId < 0 || l.OwnerId == AppResources.User.Id).OrderByDescending(l => l.LogUnixTime).ToArrayAsync();
    }

    public async Task<int> AddLog(LogItem log)
    {
        await Init();
        return await database.InsertAsync(log);
    }

    public async Task<FlowerItem[]> GetFlowers()
    {
        await Init();
        return await database.Table<FlowerItem>().ToArrayAsync();
    }

    public async Task<int> UpdateFlowers(IEnumerable<FlowerItem> flowers)
    {
        await Init();

        var ids = flowers.Select(f => f.Id).ToList();
        var count = await database.Table<FlowerItem>().DeleteAsync(f => ids.Contains(f.Id));
        await database.Table<PhotoItem>().DeleteAsync(p => p.RecordId == null && ids.Contains(p.FlowerId));

        await database.InsertAllAsync(flowers);
        foreach (var flower in flowers)
        {
            if (flower.Photos?.Length > 0)
            {
                await database.InsertAllAsync(flower.Photos);
            }
        }
        return count;
    }

    public async Task<int> SetUser(UserItem user)
    {
        await Init();
        var count = user.Id > 0 ?
            await database.Table<UserItem>().CountAsync(u => u.Id == user.Id) :
            0;
        if (count > 0)
        {
            count = await database.UpdateAsync(user);
        }
        else
        {
            count = await database.InsertAsync(user);
        }
        if (count > 0)
        {
            var c = await database.Table<ParamItem>().FirstOrDefaultAsync(p => p.Code == Constants.LastTokenName);
            c ??= new ParamItem { Code = Constants.LastTokenName };
            c.Value = user.Token;
            await database.InsertOrReplaceAsync(c);
        }
        return count;
    }
}
