using AfRApay.Web.Backend;
using AfRApay.Web.Backend.Tables;
using AfRApay.Web.Controllers.Schema;
using LinqToDB;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Filters;

namespace AfRApay.Web.Controllers;

[ApiController]
public class CardController : Controller {
	/// <summary>
	/// Links the given card to the user currently in link mode.
	/// </summary>
	/// <param name="card">The ID of the card</param>
	/// <response code="200">Returns 200 if the link succeeded</response>
	/// <response code="304">Returns 304 if the card was already linked</response>
	/// <response code="404">Returns 404 if no link process is active</response>
	[HttpPut]
	[Produces("application/json")]
	[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserResponse))]
	[ProducesResponseType(StatusCodes.Status304NotModified)]
	[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
	[SwaggerResponseExample(StatusCodes.Status200OK, typeof(UserUpdatedExample))]
	[SwaggerResponseExample(StatusCodes.Status404NotFound, typeof(ErrorNoActiveLinkProcessExample))]
	[Route("/api/card/{card}/link")]
	public async Task<IActionResult> Link(string card) {
		var db = new Database.DbConn();
		if (db.Cards.Any(p => p.CardId == card)) {
			return StatusCode(StatusCodes.Status304NotModified);
		}

		var linkFlag = db.Config.FirstOrDefault(p => p.Name == "link");

		if (string.IsNullOrWhiteSpace(linkFlag?.Value)) {
			return NotFound(new ErrorResponse("No active link process"));
		}

		var lTimeFlag = db.Config.FirstOrDefault(p => p.Name == "lTime");
		if (string.IsNullOrWhiteSpace(lTimeFlag?.Value)) {
			return NotFound(new ErrorResponse("No active link process"));
		}

		if (DateTime.UtcNow - DateTime.Parse(lTimeFlag.Value) > TimeSpan.FromMinutes(5)) {
			return NotFound(new ErrorResponse("No active link process"));
		}

		var user = db.Users.First(p => p.Id == int.Parse(linkFlag.Value));
		linkFlag.Value = "";

		await db.InsertAsync(new Card { CardId = card, UserId = user.Id });
		await db.UpdateAsync(linkFlag);
		return Ok(new UserResponse(user));
	}

	/// <summary>
	/// Creates a transaction that changes the balance of the the user the card is linked to by the specified amount.
	/// </summary>
	/// <param name="card">The ID of the card</param>
	/// <param name="ik">Random string (idempotency key) which is consistent across request retries</param>
	/// <param name="amount">Positive or negative number of cents representing the relative change in balance</param>
	/// <response code="200">Returns 200 if the transaction succeeded</response>
	/// <response code="404">Returns 404 if the card isn't linked to any account</response>
	/// <response code="412">Returns 412 if the transaction failed because the balance would be out of range after the transaction</response>
	[HttpPut]
	[Produces("application/json")]
	[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserResponse))]
	[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
	[ProducesResponseType(StatusCodes.Status412PreconditionFailed, Type = typeof(ErrorResponse))]
	[SwaggerResponseExample(200, typeof(UserUpdatedExample))]
	[SwaggerResponseExample(404, typeof(ErrorUnknownCardExample))]
	[SwaggerResponseExample(412, typeof(ErrorBalanceOutOfRangeExample))]
	[Route("/api/card/{card}/transaction/{ik}")]
	public async Task<IActionResult> Transaction(string card, string ik, [FromQuery] int amount) {
		var db = new Database.DbConn();
		if (db.Cards.Any(p => p.CardId == card)) {
			var userId = db.Cards.First(p => p.CardId == card).UserId;
			var user   = db.Users.First(p => p.Id == userId);

			if (ik == "" || ik != user.LastIdempotencyKey) {
				user.LastIdempotencyKey = ik;
				switch (user.Balance + amount) {
					case < -9999: return StatusCode(412, new ErrorResponse("Balance too low!"));
					case > 99999: return StatusCode(412, new ErrorResponse("Balance too high!"));
				}

				user.Balance += amount;
			}

			await db.UpdateAsync(user);
			return Ok(new UserResponse(user));
		}

		return NotFound(new ErrorResponse("Unknown card."));
	}

	/// <summary>
	/// Returns the balance of the the user the card is linked to.
	/// </summary>
	/// <param name="card">The ID of the card</param>
	/// <response code="200">Returns 200 if the request succeeded</response>
	/// <response code="404">Returns 404 if the card isn't linked to any account</response>
	[HttpGet]
	[Produces("application/json")]
	[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserResponse))]
	[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
	[SwaggerResponseExample(200, typeof(UserUpdatedExample))]
	[SwaggerResponseExample(404, typeof(ErrorUnknownCardExample))]
	[Route("/api/card/{card}/balance")]
	public IActionResult Balance(string card) {
		var db = new Database.DbConn();
		if (db.Cards.Any(p => p.CardId == card)) {
			var userId = db.Cards.First(p => p.CardId == card).UserId;
			var user   = db.Users.First(p => p.Id == userId);

			return Ok(new UserResponse(user));
		}

		return NotFound(new ErrorResponse("Unknown card."));
	}

	private class UserUpdatedExample : IExamplesProvider<UserResponse> {
		public UserResponse GetExamples() => new(new User { Id = 123, Nickname = "testman", Balance = 2550, LastIdempotencyKey = "5a6c94aa"});
	}

	private class ErrorUnknownCardExample : IExamplesProvider<ErrorResponse> {
		public ErrorResponse GetExamples() => new("Unknown card");
	}

	private class ErrorBalanceOutOfRangeExample : IExamplesProvider<ErrorResponse> {
		public ErrorResponse GetExamples() => new("Balance out of range");
	}

	private class ErrorNoActiveLinkProcessExample : IExamplesProvider<ErrorResponse> {
		public ErrorResponse GetExamples() => new("No active link process");
	}
}
