using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Meezi.API.Models.CustomRoles;
using Meezi.Core.Authorization;
using Meezi.Core.Entities;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Meezi.Shared;

namespace Meezi.API.Controllers;

[Route("api/cafes/{cafeId}/custom-roles")]
public class CustomRolesController : CafeApiControllerBase
{
    private readonly AppDbContext _db;

    public CustomRolesController(AppDbContext db)
    {
        _db = db;
    }

    [HttpGet]
    public async Task<IActionResult> List(string cafeId, ITenantContext tenant, CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageRoles) is { } forbidden) return forbidden;

        var roles = await _db.CustomRoles
            .AsNoTracking()
            .Where(r => r.CafeId == cafeId)
            .OrderBy(r => r.Name)
            .Select(r => new
            {
                r.Id,
                r.Name,
                r.Description,
                r.Color,
                r.PermissionsJson,
                EmployeeCount = _db.Employees.Count(e => e.CafeId == cafeId && e.CustomRoleId == r.Id && e.DeletedAt == null),
                r.CreatedAt,
            })
            .ToListAsync(ct);

        var dtos = roles.Select(r => new CustomRoleDto(
            r.Id,
            r.Name,
            r.Description,
            r.Color,
            CustomRolePermissions.Parse(r.PermissionsJson).Select(p => p.ToString()).OrderBy(p => p).ToList(),
            r.EmployeeCount,
            r.CreatedAt)).ToList();

        return Ok(new ApiResponse<IReadOnlyList<CustomRoleDto>>(true, dtos));
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(string cafeId, string id, ITenantContext tenant, CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageRoles) is { } forbidden) return forbidden;

        var r = await _db.CustomRoles.AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == id && x.CafeId == cafeId, ct);
        if (r is null) return NotFoundError("Custom role not found.");

        var employeeCount = await _db.Employees
            .CountAsync(e => e.CafeId == cafeId && e.CustomRoleId == id && e.DeletedAt == null, ct);

        return Ok(new ApiResponse<CustomRoleDto>(true, new CustomRoleDto(
            r.Id, r.Name, r.Description, r.Color,
            CustomRolePermissions.Parse(r.PermissionsJson).Select(p => p.ToString()).OrderBy(p => p).ToList(),
            employeeCount, r.CreatedAt)));
    }

    [HttpPost]
    public async Task<IActionResult> Create(
        string cafeId,
        [FromBody] CreateCustomRoleRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageRoles) is { } forbidden) return forbidden;

        var name = request.Name?.Trim() ?? string.Empty;
        if (string.IsNullOrWhiteSpace(name))
            return BadRequest(new ApiResponse<object>(false, null, new ApiError("VALIDATION_ERROR", "Name is required.", "Name")));

        var permissions = ParseAndValidatePermissions(request.Permissions);

        var role = new CustomRole
        {
            CafeId = cafeId,
            Name = name,
            Description = request.Description?.Trim(),
            Color = NormalizeColor(request.Color),
            PermissionsJson = CustomRolePermissions.Serialize(permissions),
        };

        _db.CustomRoles.Add(role);
        await _db.SaveChangesAsync(ct);

        return CreatedAtAction(nameof(Get), new { cafeId, id = role.Id },
            new ApiResponse<CustomRoleDto>(true, ToDto(role, 0)));
    }

    [HttpPatch("{id}")]
    public async Task<IActionResult> Update(
        string cafeId,
        string id,
        [FromBody] UpdateCustomRoleRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageRoles) is { } forbidden) return forbidden;

        var role = await _db.CustomRoles
            .FirstOrDefaultAsync(r => r.Id == id && r.CafeId == cafeId, ct);
        if (role is null) return NotFoundError("Custom role not found.");

        if (request.Name is not null)
        {
            var name = request.Name.Trim();
            if (string.IsNullOrWhiteSpace(name))
                return BadRequest(new ApiResponse<object>(false, null, new ApiError("VALIDATION_ERROR", "Name cannot be empty.", "Name")));
            role.Name = name;
        }

        if (request.Description is not null)
            role.Description = request.Description.Trim().Length > 0 ? request.Description.Trim() : null;

        if (request.Color is not null)
            role.Color = NormalizeColor(request.Color);

        if (request.Permissions is not null)
            role.PermissionsJson = CustomRolePermissions.Serialize(ParseAndValidatePermissions(request.Permissions));

        await _db.SaveChangesAsync(ct);

        var employeeCount = await _db.Employees
            .CountAsync(e => e.CafeId == cafeId && e.CustomRoleId == id && e.DeletedAt == null, ct);

        return Ok(new ApiResponse<CustomRoleDto>(true, ToDto(role, employeeCount)));
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(
        string cafeId,
        string id,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageRoles) is { } forbidden) return forbidden;

        var role = await _db.CustomRoles
            .FirstOrDefaultAsync(r => r.Id == id && r.CafeId == cafeId, ct);
        if (role is null) return NotFoundError("Custom role not found.");

        // Unassign employees before deletion so they fall back to their base role permissions.
        await _db.Employees
            .Where(e => e.CafeId == cafeId && e.CustomRoleId == id)
            .ExecuteUpdateAsync(s => s.SetProperty(e => e.CustomRoleId, (string?)null), ct);

        role.DeletedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(ct);

        return Ok(new ApiResponse<object>(true, null));
    }

    // ── Employee custom-role assignment ───────────────────────────────────────

    [HttpPut("/api/cafes/{cafeId}/employees/{employeeId}/custom-role")]
    public async Task<IActionResult> AssignToEmployee(
        string cafeId,
        string employeeId,
        [FromBody] AssignCustomRoleRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageRoles) is { } forbidden) return forbidden;

        var employee = await _db.Employees
            .FirstOrDefaultAsync(e => e.Id == employeeId && e.CafeId == cafeId && e.DeletedAt == null, ct);
        if (employee is null) return NotFoundError("Employee not found.");

        if (request.CustomRoleId is not null)
        {
            var roleExists = await _db.CustomRoles
                .AnyAsync(r => r.Id == request.CustomRoleId && r.CafeId == cafeId && r.DeletedAt == null, ct);
            if (!roleExists)
                return NotFoundError("Custom role not found.");
        }

        employee.CustomRoleId = request.CustomRoleId;
        await _db.SaveChangesAsync(ct);

        return Ok(new ApiResponse<object>(true, null));
    }

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

    private static CustomRoleDto ToDto(CustomRole r, int employeeCount) => new(
        r.Id, r.Name, r.Description, r.Color,
        CustomRolePermissions.Parse(r.PermissionsJson).Select(p => p.ToString()).OrderBy(p => p).ToList(),
        employeeCount, r.CreatedAt);

    private static IEnumerable<Permission> ParseAndValidatePermissions(IReadOnlyList<string>? names)
    {
        if (names is null) return [];
        return names
            .Where(n => Enum.TryParse<Permission>(n, ignoreCase: true, out _))
            .Select(n => Enum.Parse<Permission>(n, ignoreCase: true))
            .Distinct();
    }

    private static string? NormalizeColor(string? color)
    {
        if (string.IsNullOrWhiteSpace(color)) return null;
        var c = color.Trim();
        return c.StartsWith('#') ? c : null;
    }
}
