using CharacterApi.Models;
using CharacterApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Json;

namespace CharacterApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class CharactersController : ControllerBase
{
    private static readonly TimeSpan PresenceTimeout = TimeSpan.FromSeconds(45);
    private readonly CharacterStore _characters;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;
    private readonly ILogger<CharactersController> _logger;

    public CharactersController(CharacterStore characters, IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger<CharactersController> logger)
    {
        _characters = characters;
        _httpClientFactory = httpClientFactory;
        _configuration = configuration;
        _logger = logger;
    }

    [HttpPost]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> Create([FromBody] CreateCharacterRequest req)
    {
        if (string.IsNullOrWhiteSpace(req.Name))
            return BadRequest("Name required");

        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();

        var character = new Character
        {
            OwnerUserId = userId,
            Name = req.Name.Trim(),
            Coord = new Coord { X = 0, Y = 0 },
            VisionRadius = 3,
            LastSeenUtc = DateTime.UtcNow,
            CreatedUtc = DateTime.UtcNow
        };

        await _characters.CreateAsync(character);
        return Ok(character);
    }

    [HttpGet]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> ListMine()
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();

        var characters = await _characters.GetForOwnerAsync(userId);
        return Ok(characters);
    }

    [HttpGet("{id}/visible-locations")]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> VisibleLocations(string id)
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();

        var allowAnyOwner = User.IsInRole("SUPER");
        var character = await _characters.GetByIdAsync(id);
        if (character is null)
        {
            _logger.LogWarning("Visible locations request failed: character {CharacterId} was not found.", id);
            return NotFound();
        }

        if (!allowAnyOwner && character.OwnerUserId != userId)
        {
            _logger.LogWarning(
                "Visible locations request denied: character {CharacterId} belongs to owner {OwnerUserId}, request user was {UserId}",
                id,
                character.OwnerUserId,
                userId
            );
            return Forbid();
        }

        await _characters.TouchAsync(id);

        _logger.LogInformation(
            "Visible locations requested for character {CharacterId} at ({X},{Y}) radius {VisionRadius} by user {UserId}",
            character.Id,
            character.Coord.X,
            character.Coord.Y,
            character.VisionRadius,
            userId
        );

        var locationsBaseUrl = (_configuration["Services:LocationsApiBaseUrl"] ?? "http://localhost:5002").TrimEnd('/');
        var internalApiKey = (_configuration["InternalApi:Key"] ?? _configuration["Jwt:Key"] ?? string.Empty).Trim();
        var body = JsonSerializer.Serialize(new
        {
            x = character.Coord.X,
            y = character.Coord.Y,
            radius = character.VisionRadius > 0 ? character.VisionRadius : 3
        });

        var client = _httpClientFactory.CreateClient();
        using var request = new HttpRequestMessage(HttpMethod.Post, $"{locationsBaseUrl}/api/locations/internal/visible-window");
        request.Content = new StringContent(body, Encoding.UTF8, "application/json");
        request.Headers.Add("X-Internal-Api-Key", internalApiKey);

        HttpResponseMessage response;
        try
        {
            response = await client.SendAsync(request);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to reach LocationsApi while resolving visible locations for character {CharacterId}", character.Id);
            return StatusCode(StatusCodes.Status502BadGateway, new
            {
                type = "https://httpstatuses.com/502",
                title = "Bad Gateway",
                status = 502,
                detail = $"Failed to reach LocationsApi at {locationsBaseUrl}.",
                traceId = HttpContext.TraceIdentifier
            });
        }

        using (response)
        {
            var responseBody = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
                return StatusCode((int)response.StatusCode, responseBody);

            var generation = JsonSerializer.Deserialize<VisibleLocationWindowResponse>(
                responseBody,
                new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            if (generation is null)
            {
                return StatusCode(StatusCodes.Status502BadGateway, new
                {
                    type = "https://httpstatuses.com/502",
                    title = "Bad Gateway",
                    status = 502,
                    detail = "LocationsApi returned an unreadable visible locations payload.",
                    traceId = HttpContext.TraceIdentifier
                });
            }

            var locations = generation.Locations;

            _logger.LogInformation(
                "Visible locations resolved for character {CharacterId}: generated {GeneratedCount}, returned {ReturnedCount}",
                character.Id,
                generation.GeneratedCount,
                locations.Count
            );

            return Ok(locations);
        }
    }

    [HttpGet("{id}/visible-characters")]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> VisibleCharacters(string id)
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();

        var allowAnyOwner = User.IsInRole("SUPER");
        var character = await _characters.GetByIdAsync(id);
        if (character is null)
            return NotFound();
        if (!allowAnyOwner && character.OwnerUserId != userId)
            return Forbid();

        await _characters.TouchAsync(id);

        var onlineSinceUtc = DateTime.UtcNow.Subtract(PresenceTimeout);
        var visibleCharacters = await _characters.GetVisibleOthersAsync(
            id,
            character.Coord.X,
            character.Coord.Y,
            character.VisionRadius > 0 ? character.VisionRadius : 3,
            onlineSinceUtc);

        return Ok(visibleCharacters);
    }

    [HttpPost("{id}/heartbeat")]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> Heartbeat(string id)
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();

        var allowAnyOwner = User.IsInRole("SUPER");
        var character = await _characters.GetByIdAsync(id);
        if (character is null)
            return NotFound();
        if (!allowAnyOwner && character.OwnerUserId != userId)
            return Forbid();

        await _characters.TouchAsync(id);
        return Ok(new { lastSeenUtc = DateTime.UtcNow });
    }

    [HttpPut("{id}/coord")]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> UpdateCoord(string id, [FromBody] UpdateCharacterCoordRequest req)
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();
        if (req.Coord is null)
            return BadRequest("Coord required");

        var allowAnyOwner = User.IsInRole("SUPER");
        var character = await _characters.GetByIdAsync(id);
        if (character is null)
            return NotFound();
        if (!allowAnyOwner && character.OwnerUserId != userId)
            return Forbid();

        character.Coord = req.Coord;
        var updated = await _characters.UpdateCoordAsync(id, req.Coord);
        if (!updated)
            return NotFound();

        return Ok(character);
    }

    [HttpDelete("{id}")]
    [Authorize(Roles = "USER,SUPER")]
    public async Task<IActionResult> Delete(string id)
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrWhiteSpace(userId))
            return Unauthorized();

        var allowAnyOwner = User.IsInRole("SUPER");
        var deleted = await _characters.DeleteForOwnerAsync(id, userId, allowAnyOwner);
        if (!deleted)
            return NotFound();

        return Ok("Deleted");
    }
}
