using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Monitoring.Common;
using Monitoring.Controllers;
using Monitoring.Core.Domain;
using Monitoring.Core.Domain.UserPrimitives;
using Monitoring.Infrastructure.Data;
using Monitoring.Infrastructure.Services;
using Monitoring.Services;

namespace Monitoring.Features.Identity;

[Route("api/[controller]")]
public class IdentityController(
	DataContext dataContext,
	PasswordService passwordService,
	AuthenticationService authenticationService
) : BaseController
{
	public record LoginResponse(Guid Id, string Username, string Role);

	[HttpPost("login", Name = "Login")]
	[AllowAnonymous]
	[ProducesResponseType<string>(StatusCodes.Status400BadRequest)]
	[ProducesResponseType<LoginResponse>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Login(
		LoginRequest request,
		CancellationToken cancellationToken
	)
	{
		// Валидация входных данных
		if (string.IsNullOrWhiteSpace(request.Username))
		{
			return BadRequest("Username is required");
		}

		if (string.IsNullOrWhiteSpace(request.Password))
		{
			return BadRequest("Password is required");
		}

		var upperCaseUserName = UserUpperCaseName.Create(request.Username);
		var user = await dataContext
			.Users.Include(x => x.Role)
			.FirstOrDefaultAsync(x => x.UpperCaseUsername == upperCaseUserName, cancellationToken);
		if (user is null)
		{
			return BadRequest(ErrorCodes.InvalidUsernameOrPassword);
		}

		var password = Password.Create(request.Password);
		var isPasswordValid = passwordService.Verify(password, user.PasswordHash);
		if (!isPasswordValid)
		{
			return BadRequest(ErrorCodes.InvalidUsernameOrPassword);
		}

		var tokens = authenticationService.CreateTokens(user);

		// Сохраняем refresh token в базе данных
		var refreshToken = RefreshToken.Create(
			tokens.RefreshToken,
			tokens.RefreshTokenExpires,
			user.Id
		);

		dataContext.RefreshTokens.Add(refreshToken);
		await dataContext.SaveChangesAsync(cancellationToken);

		// Устанавливаем куки для токенов
		SetTokenCookies(tokens);

		return Ok(new LoginResponse(user.Id.Value, user.Username.Value, user.Role!.Name.Value));
	}

	[HttpPost("refresh-token", Name = "RefreshToken")]
	[AllowAnonymous]
	[ProducesResponseType<string>(StatusCodes.Status400BadRequest)]
	[ProducesResponseType<string>(StatusCodes.Status401Unauthorized)]
	[ProducesResponseType<LoginResponse>(StatusCodes.Status200OK)]
	public async Task<IActionResult> RefreshTokenEndpoint(CancellationToken cancellationToken)
	{
		// Получаем refresh token из запроса или из куки
		var refreshTokenString = Request.Cookies[CookieConstants.RefreshToken];
		if (string.IsNullOrEmpty(refreshTokenString))
		{
			return BadRequest("Refresh token is required");
		}

		var refreshToken = await dataContext
			.RefreshTokens.Include(rt => rt.User)
			.ThenInclude(u => u.Role)
			.AsNoTracking()
			.FirstOrDefaultAsync(rt => rt.Token == refreshTokenString, cancellationToken);
		if (refreshToken == null)
		{
			return Unauthorized("Invalid refresh token");
		}

		if (refreshToken == null || !refreshToken.IsActive)
		{
			return BadRequest("Invalid refresh token");
		}

		// Помечаем использованный refresh token
		refreshToken.MarkAsUsed();

		// Создаем новые токены
		var user = refreshToken.User;
		var tokens = authenticationService.CreateTokens(user);

		// Сохраняем новый refresh token
		var newRefreshToken = RefreshToken.Create(
			tokens.RefreshToken,
			tokens.RefreshTokenExpires,
			user.Id
		);

		dataContext.RefreshTokens.Add(newRefreshToken);
		await dataContext.SaveChangesAsync(cancellationToken);

		// Устанавливаем куки
		SetTokenCookies(tokens);

		return Ok(new LoginResponse(user.Id.Value, user.Username.Value, user.Role!.Name.Value));
	}

	[HttpPost("logout", Name = "Logout")]
	[ProducesResponseType(StatusCodes.Status200OK)]
	public IActionResult Logout()
	{
		Response.Cookies.Delete(CookieConstants.AccessToken, new CookieOptions { Path = "/" });
		Response.Cookies.Delete(CookieConstants.RefreshToken, new CookieOptions { Path = "/" });

		return Ok();
	}

	[HttpGet("me", Name = "GetMe")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<LoginResponse>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Me(CancellationToken cancellationToken)
	{
		var user = await dataContext
			.Users.Include(x => x.Role)
			.FirstOrDefaultAsync(x => x.Id == UserId, cancellationToken);
		if (user is null)
		{
			return NotFound(ErrorCodes.NotFound);
		}

		return Ok(new LoginResponse(user.Id.Value, user.Username.Value, user.Role!.Name.Value));
	}

	private void SetTokenCookies(AuthenticationService.TokenResult tokens)
	{
		Response.Cookies.Append(
			CookieConstants.AccessToken,
			tokens.AccessToken,
			new CookieOptions
			{
				HttpOnly = true,
				Secure = Request.IsHttps, // Автоматически определяем HTTPS
				SameSite = SameSiteMode.Strict,
				Path = "/",
				Expires = tokens.AccessTokenExpires,
			}
		);

		Response.Cookies.Append(
			CookieConstants.RefreshToken,
			tokens.RefreshToken,
			new CookieOptions
			{
				HttpOnly = true,
				Secure = Request.IsHttps, // Автоматически определяем HTTPS
				SameSite = SameSiteMode.Strict,
				Path = "/",
				Expires = tokens.RefreshTokenExpires,
			}
		);
	}
}
