using MemberCenter.Application.Abstractions;
using MemberCenter.Application.Models.Profile;
using MemberCenter.Infrastructure.Identity;
using MemberCenter.Web.Models.Profile;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace MemberCenter.Web.Controllers;

[Authorize]
public class ProfileController : Controller
{
    private const int MaxProfileImageDataUrlLength = 800_000;

    private static readonly HashSet<string> AllowedProfileImageContentTypes = new(StringComparer.OrdinalIgnoreCase)
    {
        "image/webp",
        "image/png",
        "image/jpeg"
    };

    private readonly IProfileService _profileService;
    private readonly INewsletterService _newsletterService;
    private readonly UserManager<ApplicationUser> _userManager;

    public ProfileController(
        IProfileService profileService,
        INewsletterService newsletterService,
        UserManager<ApplicationUser> userManager)
    {
        _profileService = profileService;
        _newsletterService = newsletterService;
        _userManager = userManager;
    }

    [HttpGet]
    public async Task<IActionResult> Index(bool edit = false)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        var profile = await _profileService.GetProfileAsync(user.Id);
        return View(await BuildIndexPageAsync(user, MapProfile(profile, user.EmailConfirmed), edit));
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Index([Bind(Prefix = "Profile")] ProfileViewModel model)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        model.Email = user.Email ?? string.Empty;
        model.EmailConfirmed = user.EmailConfirmed;
        if (!ModelState.IsValid)
        {
            return View(await BuildIndexPageAsync(user, model, isEditing: true));
        }

        try
        {
            var profile = await _profileService.SaveProfileAsync(user.Id, new SaveUserProfileRequest(
                model.LastName,
                model.FirstName,
                model.NickName,
                model.MobilePhone,
                model.LandlinePhone,
                model.DateOfBirth,
                model.Gender,
                model.CompanyName,
                model.Department,
                model.JobTitle,
                model.CompanyPhone,
                model.TaxId,
                model.InvoiceTitle,
                model.Remark));
            ViewData["Result"] = "Saved";
            return View(await BuildIndexPageAsync(user, MapProfile(profile, user.EmailConfirmed), isEditing: false));
        }
        catch (InvalidOperationException ex)
        {
            ModelState.AddModelError(string.Empty, ex.Message);
            return View(await BuildIndexPageAsync(user, model, isEditing: true));
        }
    }

    [HttpGet("profile/addresses")]
    public async Task<IActionResult> Addresses(Guid? id = null)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        var form = new AddressFormViewModel();
        if (id.HasValue)
        {
            var address = await _profileService.GetAddressAsync(user.Id, id.Value);
            if (address is not null)
            {
                form = MapAddress(address);
            }
        }

        return View(new AddressesPageViewModel { Form = form });
    }

    [HttpPost("profile/addresses")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> SaveAddress([Bind(Prefix = "Form")] AddressFormViewModel model)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        if (!ModelState.IsValid)
        {
            return View("Addresses", new AddressesPageViewModel { Form = model });
        }

        try
        {
            await _profileService.SaveAddressAsync(user.Id, new SaveUserAddressRequest(
                model.Id,
                model.Label,
                model.RecipientName,
                model.RecipientPhone,
                model.CountryCode,
                model.PostalCode,
                model.StateRegion,
                model.City,
                model.District,
                model.AddressLine1,
                model.AddressLine2,
                model.CompanyName,
                model.IsDefault,
                model.AddressMetaJson));
            return RedirectToProfileIndex();
        }
        catch (InvalidOperationException ex)
        {
            ModelState.AddModelError(string.Empty, ex.Message);
            return View("Addresses", new AddressesPageViewModel { Form = model });
        }
    }

    [HttpPost("profile/addresses/{id:guid}/delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteAddress(Guid id)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        try
        {
            await _profileService.DeleteAddressAsync(user.Id, id);
            return RedirectToProfileIndex();
        }
        catch (InvalidOperationException ex)
        {
            TempData["Error"] = ex.Message;
            return RedirectToProfileIndex();
        }
    }

    [HttpPost("profile/addresses/{id:guid}/default")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> SetDefaultAddress(Guid id)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        var address = await _profileService.GetAddressAsync(user.Id, id);
        if (address is null)
        {
            return NotFound();
        }

        try
        {
            await _profileService.SaveAddressAsync(user.Id, new SaveUserAddressRequest(
                address.Id,
                address.Label,
                address.RecipientName,
                address.RecipientPhone,
                address.CountryCode,
                address.PostalCode,
                address.StateRegion,
                address.City,
                address.District,
                address.AddressLine1,
                address.AddressLine2,
                address.CompanyName,
                true,
                address.AddressMetaJson));
            return RedirectToProfileIndex();
        }
        catch (InvalidOperationException ex)
        {
            TempData["Error"] = ex.Message;
            return RedirectToProfileIndex();
        }
    }

    [HttpGet("profile/subscriptions")]
    public async Task<IActionResult> Subscriptions()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        return View(new SubscriptionsPageViewModel
        {
            Subscriptions = await _newsletterService.ListSubscriptionsForUserAsync(user.Id)
        });
    }

    [HttpGet("profile/notifications")]
    public IActionResult Notifications()
    {
        return View();
    }

    [HttpGet("profile/avatar")]
    public async Task<IActionResult> Avatar()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return Unauthorized();
        }

        var image = await _profileService.GetProfileImageAsync(user.Id);
        if (image is null)
        {
            return NotFound();
        }

        Response.Headers.CacheControl = "private, max-age=300";
        return File(image.Content, image.ContentType);
    }

    [HttpPost("profile/avatar")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> UploadAvatar(string avatarDataUrl, string avatarContentType, int avatarWidth, int avatarHeight)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        try
        {
            var imageBytes = ParseProfileImageDataUrl(avatarDataUrl, avatarContentType);
            await _profileService.SaveProfileImageAsync(user.Id, imageBytes, avatarContentType, avatarWidth, avatarHeight);
            TempData["Result"] = "Profile image updated.";
        }
        catch (InvalidOperationException ex)
        {
            TempData["Error"] = ex.Message;
        }

        return RedirectToProfileIndex();
    }

    [HttpPost("profile/subscriptions/{id:guid}/unsubscribe")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Unsubscribe(Guid id)
    {
        var user = await _userManager.GetUserAsync(User);
        if (user is null)
        {
            return RedirectToAction("Login", "Account", new { area = string.Empty });
        }

        await _newsletterService.UnsubscribeForUserAsync(user.Id, id);
        return RedirectToAction(nameof(Subscriptions));
    }

    private static ProfileViewModel MapProfile(UserProfileDto profile, bool emailConfirmed) =>
        new()
        {
            Email = profile.Email,
            EmailConfirmed = emailConfirmed,
            LastName = profile.LastName,
            FirstName = profile.FirstName,
            NickName = profile.NickName,
            MobilePhone = profile.MobilePhone,
            LandlinePhone = profile.LandlinePhone,
            DateOfBirth = profile.DateOfBirth,
            Gender = profile.Gender,
            CompanyName = profile.CompanyName,
            Department = profile.Department,
            JobTitle = profile.JobTitle,
            CompanyPhone = profile.CompanyPhone,
            TaxId = profile.TaxId,
            InvoiceTitle = profile.InvoiceTitle,
            Remark = profile.Remark
        };

    private IActionResult RedirectToProfileIndex() =>
        RedirectToAction(nameof(Index), "Profile", new { area = string.Empty });

    private async Task<ProfileIndexPageViewModel> BuildIndexPageAsync(ApplicationUser user, ProfileViewModel profile, bool isEditing = false)
    {
        var image = await _profileService.GetProfileImageAsync(user.Id);
        return new()
        {
            Profile = profile,
            Addresses = await _profileService.ListAddressesAsync(user.Id),
            IsEditing = isEditing,
            HasProfileImage = image is not null,
            ProfileImageVersion = image?.UpdatedAt.ToUnixTimeSeconds() ?? 0
        };
    }

    private static AddressFormViewModel MapAddress(UserAddressDto address) =>
        new()
        {
            Id = address.Id,
            Label = address.Label,
            RecipientName = address.RecipientName,
            RecipientPhone = address.RecipientPhone,
            CountryCode = address.CountryCode,
            PostalCode = address.PostalCode,
            StateRegion = address.StateRegion,
            City = address.City,
            District = address.District,
            AddressLine1 = address.AddressLine1,
            AddressLine2 = address.AddressLine2,
            CompanyName = address.CompanyName,
            IsDefault = address.IsDefault,
            AddressMetaJson = address.AddressMetaJson
        };

    private static byte[] ParseProfileImageDataUrl(string? dataUrl, string? contentType)
    {
        if (string.IsNullOrWhiteSpace(dataUrl) || dataUrl.Length > MaxProfileImageDataUrlLength)
        {
            throw new InvalidOperationException("Profile image is invalid.");
        }

        if (string.IsNullOrWhiteSpace(contentType) || !AllowedProfileImageContentTypes.Contains(contentType))
        {
            throw new InvalidOperationException("Profile image type is not supported.");
        }

        var prefix = $"data:{contentType};base64,";
        if (!dataUrl.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Profile image data is invalid.");
        }

        try
        {
            return Convert.FromBase64String(dataUrl[prefix.Length..]);
        }
        catch (FormatException ex)
        {
            throw new InvalidOperationException("Profile image data is invalid.", ex);
        }
    }
}
