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

namespace Meezi.API.Controllers;

/// <summary>
/// Manage the per-branch role assignments that drive the active-branch session model.
/// Owner/Manager gated; branch-scoped managers may only touch their own branch.
/// </summary>
[Route("api/cafes/{cafeId}/employees/{employeeId}/branch-roles")]
public class StaffBranchRolesController : CafeApiControllerBase
{
    private readonly AppDbContext _db;
    private readonly Meezi.API.Services.IAuditLogService _audit;

    public StaffBranchRolesController(AppDbContext db, Meezi.API.Services.IAuditLogService audit)
    {
        _db = db;
        _audit = audit;
    }

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

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

        var data = await _db.EmployeeBranchRoles
            .Where(r => r.EmployeeId == employeeId && r.CafeId == cafeId && r.DeletedAt == null)
            .Join(_db.Branches, r => r.BranchId, b => b.Id, (r, b) => new BranchRoleAssignmentDto(r.Id, b.Id, b.Name, r.Role))
            .OrderBy(d => d.BranchName)
            .ToListAsync(ct);

        return Ok(new ApiResponse<IReadOnlyList<BranchRoleAssignmentDto>>(true, data));
    }

    [HttpPost]
    public async Task<IActionResult> Assign(
        string cafeId,
        string employeeId,
        [FromBody] AssignBranchRoleRequest request,
        ITenantContext tenant,
        CancellationToken ct)
    {
        if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied;
        if (EnsurePermission(tenant, Permission.ManageStaff) is { } forbidden) return forbidden;
        if (EnsureBranchAccess(request.BranchId, tenant) is { } branchDenied) return branchDenied;

        if (request.Role == EmployeeRole.Owner)
            return BadRequest(Error("INVALID_ROLE", "Owners are café-wide and cannot be assigned to a branch."));

        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 (employee.Role == EmployeeRole.Owner)
            return BadRequest(Error("INVALID_ROLE", "The café owner cannot hold per-branch roles."));

        var branchExists = await _db.Branches
            .AnyAsync(b => b.Id == request.BranchId && b.CafeId == cafeId && b.DeletedAt == null, ct);
        if (!branchExists) return NotFoundError("Branch not found.");

        var existing = await _db.EmployeeBranchRoles
            .FirstOrDefaultAsync(r => r.EmployeeId == employeeId && r.BranchId == request.BranchId && r.DeletedAt == null, ct);
        if (existing is not null)
            return Conflict(new ApiResponse<object>(false, null,
                new ApiError("ALREADY_ASSIGNED", "This employee already has a role in this branch. Update it instead.")));

        var assignment = new EmployeeBranchRole
        {
            CafeId = cafeId,
            EmployeeId = employeeId,
            BranchId = request.BranchId,
            Role = request.Role,
        };
        _db.EmployeeBranchRoles.Add(assignment);
        await _db.SaveChangesAsync(ct);

        var branchName = await _db.Branches
            .Where(b => b.Id == request.BranchId)
            .Select(b => b.Name)
            .FirstAsync(ct);

        await _audit.LogAsync(new Meezi.API.Services.AuditEntry
        {
            Category = "Staff",
            Action = "BranchRoleAssigned",
            EntityType = "Employee",
            EntityId = employeeId,
            BranchId = request.BranchId,
            Summary = $"Assigned {request.Role} role in {branchName} to {employee.Name}",
            Details = new { employeeId, branchId = request.BranchId, role = request.Role.ToString() }
        }, ct);

        return Ok(new ApiResponse<BranchRoleAssignmentDto>(true,
            new BranchRoleAssignmentDto(assignment.Id, request.BranchId, branchName, request.Role)));
    }

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

        if (request.Role == EmployeeRole.Owner)
            return BadRequest(Error("INVALID_ROLE", "Owners are café-wide and cannot be assigned to a branch."));

        var assignment = await _db.EmployeeBranchRoles
            .FirstOrDefaultAsync(r => r.Id == assignmentId && r.EmployeeId == employeeId
                && r.CafeId == cafeId && r.DeletedAt == null, ct);
        if (assignment is null) return NotFoundError("Branch role assignment not found.");

        if (EnsureBranchAccess(assignment.BranchId, tenant) is { } branchDenied) return branchDenied;

        assignment.Role = request.Role;
        await _db.SaveChangesAsync(ct);

        var branchName = await _db.Branches
            .Where(b => b.Id == assignment.BranchId)
            .Select(b => b.Name)
            .FirstAsync(ct);

        await _audit.LogAsync(new Meezi.API.Services.AuditEntry
        {
            Category = "Staff",
            Action = "BranchRoleUpdated",
            EntityType = "Employee",
            EntityId = employeeId,
            BranchId = assignment.BranchId,
            Summary = $"Changed role to {request.Role} in {branchName}",
            Details = new { employeeId, branchId = assignment.BranchId, role = request.Role.ToString() }
        }, ct);

        return Ok(new ApiResponse<BranchRoleAssignmentDto>(true,
            new BranchRoleAssignmentDto(assignment.Id, assignment.BranchId, branchName, assignment.Role)));
    }

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

        var assignment = await _db.EmployeeBranchRoles
            .FirstOrDefaultAsync(r => r.Id == assignmentId && r.EmployeeId == employeeId
                && r.CafeId == cafeId && r.DeletedAt == null, ct);
        if (assignment is null) return NotFoundError("Branch role assignment not found.");

        if (EnsureBranchAccess(assignment.BranchId, tenant) is { } branchDenied) return branchDenied;

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

        await _audit.LogAsync(new Meezi.API.Services.AuditEntry
        {
            Category = "Staff",
            Action = "BranchRoleRemoved",
            EntityType = "Employee",
            EntityId = employeeId,
            BranchId = assignment.BranchId,
            Summary = $"Removed {assignment.Role} branch role",
            Details = new { employeeId, branchId = assignment.BranchId, role = assignment.Role.ToString() }
        }, ct);

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

    private static ApiResponse<object> Error(string code, string message) =>
        new(false, null, new ApiError(code, message));
}
