using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Ikar.Server.Data;
using Ikar.Server.Infrastructure.Admin;
using Ikar.Shared;

namespace Ikar.Server.Controllers;

[ApiController]
[Route("admin/api")]
public sealed class AdminController(
    IOptions<AdminOptions> adminOptions,
    AdminService adminService,
    IkarDbContext dbContext) : ControllerBase
{
    [HttpGet("session")]
    [AllowAnonymous]
    public async Task<ActionResult<AdminSessionStateDto>> GetSessionState()
    {
        var auth = await HttpContext.AuthenticateAsync(AdminAuthConstants.Scheme);
        return Ok(new AdminSessionStateDto(
            adminOptions.Value.IsConfigured,
            auth.Succeeded,
            auth.Principal?.Identity?.Name));
    }

    [HttpPost("session/login")]
    [AllowAnonymous]
    public async Task<IActionResult> Login(AdminLoginRequest request)
    {
        if (!adminOptions.Value.IsConfigured)
        {
            return StatusCode(StatusCodes.Status503ServiceUnavailable, "Admin login is not configured.");
        }

        var username = request.Username?.Trim() ?? string.Empty;
        var password = request.Password ?? string.Empty;
        if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
        {
            return ValidationProblem("Username and password are required.");
        }

        if (!SecureEquals(adminOptions.Value.Username, username) ||
            !SecureEquals(adminOptions.Value.Password, password))
        {
            return Unauthorized("Invalid admin credentials.");
        }

        var claims = new List<Claim>
        {
            new(ClaimTypes.Name, adminOptions.Value.Username),
            new(ClaimTypes.Role, "Admin")
        };

        var identity = new ClaimsIdentity(claims, AdminAuthConstants.Scheme);
        var principal = new ClaimsPrincipal(identity);

        await HttpContext.SignInAsync(AdminAuthConstants.Scheme, principal);
        return NoContent();
    }

    [HttpPost("session/logout")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> Logout()
    {
        await HttpContext.SignOutAsync(AdminAuthConstants.Scheme);
        return NoContent();
    }

    [HttpGet("status")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminStatusDto>> GetStatus(CancellationToken cancellationToken) =>
        Ok(await adminService.GetStatusAsync(cancellationToken));

    [HttpGet("app-updates/compatibility-report")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminAndroidUpdateCompatibilityReportDto>> GetAndroidUpdateCompatibilityReport(CancellationToken cancellationToken) =>
        Ok(await adminService.GetAndroidUpdateCompatibilityReportAsync(cancellationToken));

    [HttpGet("users")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<IReadOnlyList<AdminUserDto>>> GetUsers([FromQuery] string? q, CancellationToken cancellationToken) =>
        Ok(await adminService.GetUsersAsync(q, cancellationToken));

    [HttpPost("users/{userId:guid}/block")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> BlockUser(Guid userId, CancellationToken cancellationToken)
    {
        var updated = await adminService.SetUserBlockedAsync(userId, true, cancellationToken);
        return updated ? NoContent() : NotFound();
    }

    [HttpPost("users/{userId:guid}/unblock")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> UnblockUser(Guid userId, CancellationToken cancellationToken)
    {
        var updated = await adminService.SetUserBlockedAsync(userId, false, cancellationToken);
        return updated ? NoContent() : NotFound();
    }

    [HttpDelete("users/{userId:guid}")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> DeleteUser(Guid userId, CancellationToken cancellationToken)
    {
        var deleted = await adminService.DeleteUserAsync(userId, cancellationToken);
        return deleted ? NoContent() : NotFound();
    }

    [HttpGet("chats")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<IReadOnlyList<AdminChatDto>>> GetChats([FromQuery] string? q, CancellationToken cancellationToken) =>
        Ok(await adminService.GetChatsAsync(q, cancellationToken));

    [HttpGet("chats/{chatId:guid}/details")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminChatDetailsDto>> GetChatDetails(Guid chatId, CancellationToken cancellationToken)
    {
        var details = await adminService.GetChatDetailsAsync(chatId, cancellationToken);
        return details is null ? NotFound() : Ok(details);
    }

    [HttpPut("chats/{chatId:guid}/settings")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminChatDetailsDto>> UpdateChatSettings(
        Guid chatId,
        AdminChatSettingsRequest request,
        CancellationToken cancellationToken)
    {
        try
        {
            var details = await adminService.UpdateChatSettingsAsync(chatId, request, cancellationToken);
            return details is null ? NotFound() : Ok(details);
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }

    [HttpPut("chats/{chatId:guid}/members/{memberUserId:guid}/permissions")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminChatDetailsDto>> UpdateChatMemberPermissions(
        Guid chatId,
        Guid memberUserId,
        AdminChatMemberPermissionsRequest request,
        CancellationToken cancellationToken)
    {
        var details = await adminService.UpdateMemberPermissionsAsync(chatId, memberUserId, request, cancellationToken);
        return details is null ? NotFound() : Ok(details);
    }

    [HttpPost("chats/{chatId:guid}/invite-links")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminChatDetailsDto>> CreateInviteLink(
        Guid chatId,
        AdminCreateInviteLinkRequest request,
        CancellationToken cancellationToken)
    {
        try
        {
            var details = await adminService.CreateInviteLinkAsync(chatId, request, cancellationToken);
            return details is null ? NotFound() : Ok(details);
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }

    [HttpDelete("chats/{chatId:guid}/invite-links/{inviteLinkId:guid}")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminChatDetailsDto>> RevokeInviteLink(
        Guid chatId,
        Guid inviteLinkId,
        CancellationToken cancellationToken)
    {
        var details = await adminService.RevokeInviteLinkAsync(chatId, inviteLinkId, cancellationToken);
        return details is null ? NotFound() : Ok(details);
    }

    [HttpPost("chats/{chatId:guid}/join-requests/{joinRequestId:guid}")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminChatDetailsDto>> ResolveJoinRequest(
        Guid chatId,
        Guid joinRequestId,
        AdminResolveJoinRequestRequest request,
        CancellationToken cancellationToken)
    {
        var details = await adminService.ResolveJoinRequestAsync(chatId, joinRequestId, request.Approve, cancellationToken);
        return details is null ? NotFound() : Ok(details);
    }

    [HttpDelete("chats/{chatId:guid}")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> DeleteChat(Guid chatId, CancellationToken cancellationToken)
    {
        var deleted = await adminService.DeleteChatAsync(chatId, cancellationToken);
        return deleted ? NoContent() : NotFound();
    }

    [HttpGet("attachments")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<IReadOnlyList<AdminAttachmentDto>>> GetAttachments([FromQuery] string? q, CancellationToken cancellationToken) =>
        Ok(await adminService.GetAttachmentsAsync(q, cancellationToken));

    [HttpGet("attachments/{attachmentId:guid}/content")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> DownloadAttachment(Guid attachmentId, CancellationToken cancellationToken)
    {
        var attachment = await dbContext.MessageAttachments
            .AsNoTracking()
            .SingleOrDefaultAsync(x => x.Id == attachmentId, cancellationToken);

        if (attachment is null || !System.IO.File.Exists(attachment.StoragePath))
        {
            return NotFound();
        }

        var stream = System.IO.File.OpenRead(attachment.StoragePath);
        var contentType = string.IsNullOrWhiteSpace(attachment.ContentType)
            ? "application/octet-stream"
            : attachment.ContentType;

        return File(stream, contentType, attachment.OriginalFileName);
    }

    [HttpDelete("attachments/{attachmentId:guid}")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<IActionResult> DeleteAttachment(Guid attachmentId, CancellationToken cancellationToken)
    {
        var deleted = await adminService.DeleteAttachmentAsync(attachmentId, cancellationToken);
        return deleted ? NoContent() : NotFound();
    }

    [HttpGet("moderation-log")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<IReadOnlyList<AdminModerationLogItemDto>>> GetModerationLog(
        [FromQuery] Guid? chatId,
        [FromQuery] int limit = 100,
        CancellationToken cancellationToken = default)
        => Ok(await adminService.GetModerationLogAsync(chatId, limit, cancellationToken));

    [HttpPost("messages/cleanup-expired")]
    [Authorize(Policy = AdminAuthConstants.Policy)]
    public async Task<ActionResult<AdminExpiredMessagesCleanupDto>> CleanupExpiredMessages(CancellationToken cancellationToken) =>
        Ok(await adminService.CleanupExpiredMessagesAsync(cancellationToken));

    private static bool SecureEquals(string left, string right)
    {
        var leftBytes = System.Text.Encoding.UTF8.GetBytes(left ?? string.Empty);
        var rightBytes = System.Text.Encoding.UTF8.GetBytes(right ?? string.Empty);
        return CryptographicOperations.FixedTimeEquals(leftBytes, rightBytes);
    }

    public sealed record AdminLoginRequest(string? Username, string? Password);
}
