using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Members;
using ROLAC.API.DTOs.Shared;
using ROLAC.API.Entities;

namespace ROLAC.API.Services;

public class MemberService : IMemberService
{
    private readonly AppDbContext         _db;
    private readonly IHttpContextAccessor _http;

    public MemberService(AppDbContext db, IHttpContextAccessor http)
    {
        _db   = db;
        _http = http;
    }

    private string CurrentUserId =>
        _http.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "system";

    // ── GetPaged ─────────────────────────────────────────────────────────────

    public async Task<PagedResult<MemberListItemDto>> GetPagedAsync(
        int page, int pageSize, string? search, string? status, bool? hasUser)
    {
        var query = _db.Members.AsQueryable();

        if (!string.IsNullOrWhiteSpace(search))
        {
            var s = search.Trim().ToLower();
            query = query.Where(m =>
                m.FirstName_en.ToLower().Contains(s) ||
                m.LastName_en.ToLower().Contains(s)  ||
                (m.NickName     != null && m.NickName.ToLower().Contains(s))     ||
                (m.FirstName_zh != null && m.FirstName_zh.Contains(search))      ||
                (m.LastName_zh  != null && m.LastName_zh.Contains(search))       ||
                (m.Entity       != null && m.Entity.ToLower().Contains(s))       ||
                (m.Email        != null && m.Email.ToLower().Contains(s)));
        }

        if (!string.IsNullOrWhiteSpace(status))
            query = query.Where(m => m.Status == status);

        // Build a lookup of MemberId → UserId for hasUser filtering and projection.
        // Done as a separate query so InMemory provider works without cross-DbSet joins.
        var memberUserMap = await _db.Users
            .Where(u => u.MemberId != null)
            .Select(u => new { u.MemberId, u.Id })
            .ToDictionaryAsync(u => u.MemberId!.Value, u => u.Id);

        if (hasUser.HasValue)
        {
            var linkedMemberIds = memberUserMap.Keys.ToHashSet();
            query = hasUser.Value
                ? query.Where(m => linkedMemberIds.Contains(m.Id))
                : query.Where(m => !linkedMemberIds.Contains(m.Id));
        }

        var total = await query.CountAsync();
        var members = await query
            .OrderBy(m => m.LastName_en).ThenBy(m => m.FirstName_en)
            .Skip((page - 1) * pageSize).Take(pageSize)
            .AsNoTracking()
            .ToListAsync();

        var items = members.Select(m => new MemberListItemDto
        {
            Id           = m.Id,
            FirstName_en = m.FirstName_en,
            LastName_en  = m.LastName_en,
            NickName     = m.NickName,
            FirstName_zh = m.FirstName_zh,
            LastName_zh  = m.LastName_zh,
            Entity       = m.Entity,
            Status       = m.Status,
            Email        = m.Email,
            PhoneCell    = m.PhoneCell,
            JoinDate     = m.JoinDate,
            LinkedUserId = memberUserMap.TryGetValue(m.Id, out var uid) ? uid : null,
        }).ToList();

        return new PagedResult<MemberListItemDto>
            { Items = items, TotalCount = total, Page = page, PageSize = pageSize };
    }

    // ── GetById ──────────────────────────────────────────────────────────────

    public async Task<MemberDto?> GetByIdAsync(int id)
    {
        var m = await _db.Members
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == id);

        if (m is null) return null;

        // Separate lookup for linked user — avoids InMemory cross-DbSet join issues.
        var linkedUser = await _db.Users
            .Where(u => u.MemberId == id)
            .Select(u => u.Id)
            .FirstOrDefaultAsync();

        return new MemberDto
        {
            Id = m.Id, FirstName_en = m.FirstName_en, LastName_en = m.LastName_en,
            NickName = m.NickName, FirstName_zh = m.FirstName_zh, LastName_zh = m.LastName_zh,
            Entity = m.Entity,
            Gender = m.Gender, DateOfBirth = m.DateOfBirth, BaptismDate = m.BaptismDate,
            BaptismChurch = m.BaptismChurch, Email = m.Email, PhoneCell = m.PhoneCell,
            PhoneHome = m.PhoneHome, Address = m.Address, City = m.City, State = m.State,
            ZipCode = m.ZipCode, Country = m.Country, PhotoBlobPath = m.PhotoBlobPath,
            Status = m.Status, LanguagePreference = m.LanguagePreference, JoinDate = m.JoinDate,
            Notes = m.Notes, FamilyUnitId = m.FamilyUnitId,
            LinkedUserId = linkedUser,
            CreatedAt = m.CreatedAt, UpdatedAt = m.UpdatedAt,
        };
    }

    // ── Create ───────────────────────────────────────────────────────────────

    public async Task<int> CreateAsync(CreateMemberRequest r)
    {
        var member = MapFromRequest(r);
        _db.Members.Add(member);
        await _db.SaveChangesAsync();
        return member.Id;
    }

    // ── Update ───────────────────────────────────────────────────────────────

    public async Task UpdateAsync(int id, UpdateMemberRequest r)
    {
        var m = await _db.Members.FindAsync(id)
            ?? throw new KeyNotFoundException($"Member {id} not found.");
        ApplyRequest(m, r);
        await _db.SaveChangesAsync();
    }

    // ── Delete (soft) ────────────────────────────────────────────────────────

    public async Task DeleteAsync(int id)
    {
        var m = await _db.Members.FindAsync(id)
            ?? throw new KeyNotFoundException($"Member {id} not found.");
        m.IsDeleted = true;
        m.DeletedAt = DateTimeOffset.UtcNow;
        m.DeletedBy = CurrentUserId;
        await _db.SaveChangesAsync();
        // Detach so that subsequent FindAsync calls bypass the identity cache and
        // hit the store — where the global query filter (IsDeleted = false) applies.
        _db.Entry(m).State = Microsoft.EntityFrameworkCore.EntityState.Detached;
    }

    // ── Helpers ──────────────────────────────────────────────────────────────

    private static Member MapFromRequest(CreateMemberRequest r) => new()
    {
        FirstName_en = r.FirstName_en, LastName_en = r.LastName_en,
        NickName = r.NickName, FirstName_zh = r.FirstName_zh, LastName_zh = r.LastName_zh,
        Entity = r.Entity,
        Gender = r.Gender, DateOfBirth = r.DateOfBirth, BaptismDate = r.BaptismDate,
        BaptismChurch = r.BaptismChurch, Email = r.Email, PhoneCell = r.PhoneCell,
        PhoneHome = r.PhoneHome, Address = r.Address, City = r.City, State = r.State,
        ZipCode = r.ZipCode, Country = r.Country, Status = r.Status,
        LanguagePreference = r.LanguagePreference, JoinDate = r.JoinDate,
        Notes = r.Notes, FamilyUnitId = r.FamilyUnitId,
    };

    private static void ApplyRequest(Member m, CreateMemberRequest r)
    {
        m.FirstName_en = r.FirstName_en; m.LastName_en = r.LastName_en;
        m.NickName = r.NickName; m.FirstName_zh = r.FirstName_zh; m.LastName_zh = r.LastName_zh;
        m.Entity = r.Entity;
        m.Gender = r.Gender; m.DateOfBirth = r.DateOfBirth; m.BaptismDate = r.BaptismDate;
        m.BaptismChurch = r.BaptismChurch; m.Email = r.Email; m.PhoneCell = r.PhoneCell;
        m.PhoneHome = r.PhoneHome; m.Address = r.Address; m.City = r.City; m.State = r.State;
        m.ZipCode = r.ZipCode; m.Country = r.Country; m.Status = r.Status;
        m.LanguagePreference = r.LanguagePreference; m.JoinDate = r.JoinDate;
        m.Notes = r.Notes; m.FamilyUnitId = r.FamilyUnitId;
    }
}
