using CharacterApi.Models;
using MongoDB.Driver;

namespace CharacterApi.Services;

public class CharacterStore
{
    private readonly IMongoCollection<Character> _col;

    public CharacterStore(IConfiguration cfg)
    {
        var cs = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017";
        var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb";
        var client = new MongoClient(cs);
        var db = client.GetDatabase(dbName);
        _col = db.GetCollection<Character>("Characters");

        var ownerIndex = Builders<Character>.IndexKeys.Ascending(c => c.OwnerUserId);
        _col.Indexes.CreateOne(new CreateIndexModel<Character>(ownerIndex));
        var coordIndex = Builders<Character>.IndexKeys.Ascending("Coord.X").Ascending("Coord.Y");
        _col.Indexes.CreateOne(new CreateIndexModel<Character>(coordIndex));
    }

    public Task CreateAsync(Character character) => _col.InsertOneAsync(character);

    public Task<List<Character>> GetForOwnerAsync(string ownerUserId) =>
        _col.Find(c => c.OwnerUserId == ownerUserId).ToListAsync();

    public async Task<Character?> GetByIdAsync(string id) =>
        await _col.Find(c => c.Id == id).FirstOrDefaultAsync();

    public async Task<bool> UpdateCoordAsync(string id, Coord coord)
    {
        var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
        var update = Builders<Character>.Update
            .Set(c => c.Coord, coord)
            .Set(c => c.LastSeenUtc, DateTime.UtcNow);
        var result = await _col.UpdateOneAsync(filter, update);
        return result.ModifiedCount > 0 || result.MatchedCount > 0;
    }

    public async Task<bool> TouchAsync(string id)
    {
        var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
        var update = Builders<Character>.Update.Set(c => c.LastSeenUtc, DateTime.UtcNow);
        var result = await _col.UpdateOneAsync(filter, update);
        return result.ModifiedCount > 0 || result.MatchedCount > 0;
    }

    public Task<List<VisibleCharacter>> GetVisibleOthersAsync(string characterId, int x, int y, int radius, DateTime onlineSinceUtc) =>
        _col.Find(c =>
                c.Id != characterId &&
                c.Coord.X >= x - radius &&
                c.Coord.X <= x + radius &&
                c.Coord.Y >= y - radius &&
                c.Coord.Y <= y + radius &&
                c.LastSeenUtc >= onlineSinceUtc)
            .Project(c => new VisibleCharacter
            {
                Id = c.Id ?? string.Empty,
                Name = c.Name,
                Coord = c.Coord,
                LastSeenUtc = c.LastSeenUtc
            })
            .ToListAsync();

    public async Task<bool> DeleteForOwnerAsync(string id, string ownerUserId, bool allowAnyOwner)
    {
        var filter = Builders<Character>.Filter.Eq(c => c.Id, id);
        if (!allowAnyOwner)
            filter = Builders<Character>.Filter.And(filter, Builders<Character>.Filter.Eq(c => c.OwnerUserId, ownerUserId));

        var result = await _col.DeleteOneAsync(filter);
        return result.DeletedCount > 0;
    }
}
