namespace MyOffice.Web.Controllers;

using Models.Auth;
using Identity.Domain;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using MyOffice.Data.Models.Users;
using Core.Extensions;
using MyOffice.Core.Identity;
using Core;
using Services.Users;
using Services.Users.Domain;
using MyOffice.Data.Models.Currencies;
using MyOffice.Web.Infrastructure.Attributes;

[ApiController]
[Route("api/user")]
[DefaultFromBody]
public class UserController : BaseApiController
{
	private readonly UserManager<ApplicationUser<Guid>> _userManager;
	private readonly UserService _userService;
	private readonly IEnumerable<IExternalProviderValidator> _externalProviderValidators;

	public UserController(
		UserManager<ApplicationUser<Guid>> userManager,
		UserService userService,
		IEnumerable<IExternalProviderValidator> externalProviderValidators
	)
	{
		_userManager = userManager;
		_userService = userService;
		_externalProviderValidators = externalProviderValidators;
	}

	[AllowAnonymous]
	[HttpPost("register")]
	public async Task<IActionResult> Register([FromBody] RegisterModel request)
	{
		if (!ModelState.IsValid)
			return BadRequest(new
			{
				Succeeded = false,
				Errors = ModelState.Values
					.SelectMany(v => v.Errors)
					.Select(e => new { Code = "Validation", Description = e.ErrorMessage })
			});

		var email = request.UserName.Trim();
		var user = new ApplicationUser<Guid>
		{
			Id = Guid.NewGuid(),
			UserName = email,
			Email = email,
			IsEmailConfirmed = true,
			CurrencyId = CurrencyGlobalIdEnum.USD.ToString(),
		};

		var result = await _userManager.CreateAsync(user, request.Password);

		return Ok(new
		{
			result.Succeeded,
			Errors = result.Errors
				.Where(x => !x.Code.Equals("DuplicateUserName"))
				.Select(x => new { x.Code, x.Description })
		});
	}

	[Authorize]
	[HttpGet("profile")]
	public async Task<object> ProfileGet()
	{
		var user = await _userManager.FindByIdAsync(UserId.ToString());

		return user.ToModel(await _userService.GetUserExternals(user.Id));
	}

	[Authorize]
	[HttpPost("profile")]
	public async Task<object> ProfileUpdate([FromBody] ProfileModel model)
	{
		var user = await _userManager.FindByIdAsync(UserId.ToString()); ;

		user.FirstName = model.FirstName;
		user.LastName = model.LastName;
		user.FullName = model.FullName.NullIfEmpty() ?? $"{model.FirstName} {model.LastName}";
		user.CurrencyId = model.Currency;

		await _userManager.UpdateAsync(user);

		return user.ToModel(await _userService.GetUserExternals(user.Id));
	}

	[Authorize]
	[HttpPost("attach")]
	public async Task<object> AttachProvider([FromBody] AttachModel model)
	{
		var validator = _externalProviderValidators.FirstOrDefault(x => x.Provider.EqualsIgnoreCase(model.Provider));
		if (validator == null) return ProblemBadResponse("Provider not supported");

		var result = await validator.ValidateAsync(model.Token);
		if (!result.IsSuccessed) return ProblemBadResponse("Token not valid");

		var execResult = await _userService.AddUserExternalAsync(UserId, model.Provider, result.ExternalId!, result.Email!);
		switch (execResult.Status)
		{
			case AddUserExternalStatusEnum.success:
				return Ok(new
				{
					success = true
				});

			case AddUserExternalStatusEnum.externalid_used:
			case AddUserExternalStatusEnum.user_not_valid:
				return ProblemBadResponse("Provider already connected");

			default:
				throw new NotSupportedException(execResult.Status.ToString());
		}
	}

	[Authorize]
	[HttpPost("deattach")]
	public async Task<object> DeattachProvider([FromBody] DeattachModel model)
	{
		var exec = await _userService.RemoveUserExternal(UserId, model.Provider);

		if (exec.Status == GeneralExecStatus.not_found) return ProblemBadResponse("Provider not connected");

		return Ok(new
		{
			success = true
		});
	}
}

public class DeattachModel
{
	[Required] public string Provider { get; set; } = null!;
}

public class AttachModel
{
	[Required] public string Provider { get; set; } = null!;
	[Required] public string Token { get; set; } = null!;
}

public class ProfileModel
{
	public class ProviderModel
	{
		public string Provider { get; set; } = null!;

		public DateTime CreatedOn { get; set; }
	}

	public string? Email { get; set; }
	public string? FirstName { get; set; }
	public string? LastName { get; set; }
	public string? FullName { get; set; }
	public bool? IsEmailConfirmed { get; set; }
	public string? Currency { get; set; }
	public List<ProviderModel>? Providers { get; set; }
}

public static class ProfileModelExtensions
{
	public static ProfileModel ToModel(this ApplicationUser<Guid> user, List<UserExternal> userClaims)
	{
		return new ProfileModel
		{
			Email = user.Email,
			FirstName = user.FirstName,
			LastName = user.LastName,
			FullName = user.FullName,
			IsEmailConfirmed = user.IsEmailConfirmed,
			Currency = user.CurrencyId,
			Providers = userClaims?.Select(x => new ProfileModel.ProviderModel
			{
				Provider = x.Provider,
				CreatedOn = x.CreatedOn
			}).ToList()
		};
	}
}