using System.Diagnostics;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using MemberCenter.TestSite.Models;
using Microsoft.AspNetCore.Mvc;

namespace MemberCenter.TestSite.Controllers;

public class HomeController : Controller
{
    private const string UserAccessTokenKey = "user_access_token";
    private const string UserRefreshTokenKey = "user_refresh_token";
    private const string ServiceAccessTokenKey = "service_access_token";
    private const string CodeVerifierKey = "code_verifier";
    private const string LastTitleKey = "last_title";
    private const string LastBodyKey = "last_body";

    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        WriteIndented = true
    };

    private readonly IConfiguration _configuration;
    private readonly IHttpClientFactory _httpClientFactory;

    public HomeController(IConfiguration configuration, IHttpClientFactory httpClientFactory)
    {
        _configuration = configuration;
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet("")]
    public IActionResult Index()
    {
        return View(BuildViewModel());
    }

    [HttpPost("auth/redirect-login")]
    public IActionResult RedirectLogin()
    {
        var options = GetOptions();
        if (string.IsNullOrWhiteSpace(options.WebLoginClientId))
        {
            StoreResult("Redirect login", "Configure MemberCenter:WebLoginClientId first.");
            return RedirectToAction(nameof(Index));
        }

        var codeVerifier = Base64Url(RandomNumberGenerator.GetBytes(32));
        var codeChallenge = Base64Url(SHA256.HashData(Encoding.ASCII.GetBytes(codeVerifier)));
        HttpContext.Session.SetString(CodeVerifierKey, codeVerifier);

        var callbackUrl = Url.ActionLink(nameof(Callback), "Home");
        var query = new Dictionary<string, string?>
        {
            ["client_id"] = options.WebLoginClientId,
            ["redirect_uri"] = callbackUrl,
            ["response_type"] = "code",
            ["scope"] = options.WebLoginScopes,
            ["code_challenge"] = codeChallenge,
            ["code_challenge_method"] = "S256",
            ["state"] = Guid.NewGuid().ToString("N")
        };

        return Redirect($"{TrimSlash(options.ApiBaseUrl)}/oauth/authorize?{BuildQuery(query)}");
    }

    [HttpGet("auth/callback")]
    public async Task<IActionResult> Callback([FromQuery] string? code, [FromQuery] string? error, [FromQuery] string? error_description)
    {
        if (!string.IsNullOrWhiteSpace(error))
        {
            StoreResult("Redirect callback", JsonSerializer.Serialize(new { error, error_description }, JsonOptions));
            return RedirectToAction(nameof(Index));
        }

        if (string.IsNullOrWhiteSpace(code))
        {
            StoreResult("Redirect callback", "Missing authorization code.");
            return RedirectToAction(nameof(Index));
        }

        var options = GetOptions();
        var codeVerifier = HttpContext.Session.GetString(CodeVerifierKey);
        if (string.IsNullOrWhiteSpace(codeVerifier))
        {
            StoreResult("Redirect callback", "Missing code verifier in test site session.");
            return RedirectToAction(nameof(Index));
        }

        var form = new Dictionary<string, string?>
        {
            ["grant_type"] = "authorization_code",
            ["client_id"] = options.WebLoginClientId,
            ["code"] = code,
            ["redirect_uri"] = Url.ActionLink(nameof(Callback), "Home"),
            ["code_verifier"] = codeVerifier
        };

        await ExchangeUserTokenAsync("Redirect login token", form);
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("auth/logout")]
    public IActionResult Logout()
    {
        var options = GetOptions();
        var callbackUrl = Url.ActionLink(nameof(LogoutCallback), "Home");
        HttpContext.Session.Remove(UserAccessTokenKey);
        HttpContext.Session.Remove(UserRefreshTokenKey);

        if (string.IsNullOrWhiteSpace(callbackUrl))
        {
            StoreResult("Redirect logout", "Unable to build logout callback URL.");
            return RedirectToAction(nameof(Index));
        }

        return Redirect($"{TrimSlash(options.WebBaseUrl)}/account/logout?returnUrl={Uri.EscapeDataString(callbackUrl)}");
    }

    [HttpGet("auth/logout-callback")]
    public IActionResult LogoutCallback()
    {
        HttpContext.Session.Remove(UserAccessTokenKey);
        HttpContext.Session.Remove(UserRefreshTokenKey);
        StoreResult("Redirect logout", "Member Center session cleared and TestSite user tokens removed.");
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("auth/service-token")]
    public async Task<IActionResult> ServiceToken(string? clientId, string? clientSecret, string? scopes)
    {
        var options = GetOptions();
        var form = new Dictionary<string, string?>
        {
            ["grant_type"] = "client_credentials",
            ["client_id"] = string.IsNullOrWhiteSpace(clientId) ? options.ServiceClientId : clientId,
            ["client_secret"] = string.IsNullOrWhiteSpace(clientSecret) ? options.ServiceClientSecret : clientSecret,
            ["scope"] = string.IsNullOrWhiteSpace(scopes) ? options.ServiceScopes : scopes
        };

        var response = await PostFormAsync("/oauth/token", form);
        var body = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            SaveToken(body, ServiceAccessTokenKey, null);
        }

        StoreResult("Service token", FormatResponse(response, body));
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("tests/user-profile-get")]
    public async Task<IActionResult> UserProfileGet()
    {
        await SendUserAsync("GET /user/profile", HttpMethod.Get, "/user/profile");
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("tests/user-profile-post")]
    public async Task<IActionResult> UserProfilePost()
    {
        var payload = new
        {
            LastName = "Test",
            FirstName = "User",
            NickName = "Member Center Test",
            MobilePhone = "+886900000000",
            LandlinePhone = "02-00000000",
            DateOfBirth = "1990-01-01",
            Gender = "unspecified",
            CompanyName = "Test Company",
            Department = "QA",
            JobTitle = "Tester",
            CompanyPhone = "02-11111111",
            TaxId = "00000000",
            InvoiceTitle = "Test Company",
            Remark = "Updated by MemberCenter.TestSite"
        };
        await SendUserAsync("POST /user/profile", HttpMethod.Post, "/user/profile", payload);
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("tests/user-addresses-get")]
    public async Task<IActionResult> UserAddressesGet()
    {
        await SendUserAsync("GET /user/addresses", HttpMethod.Get, "/user/addresses");
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("tests/user-addresses-post")]
    public async Task<IActionResult> UserAddressesPost()
    {
        var payload = new
        {
            Id = (Guid?)null,
            Label = "test-site",
            RecipientName = "Test User",
            RecipientPhone = "+886900000000",
            CountryCode = "TW",
            PostalCode = "100",
            StateRegion = "",
            City = "Taipei",
            District = "Zhongzheng",
            AddressLine1 = "No. 1, Test Road",
            AddressLine2 = "Test Building",
            CompanyName = "Test Company",
            IsDefault = true,
            AddressMetaJson = "{\"source\":\"MemberCenter.TestSite\"}"
        };
        await SendUserAsync("POST /user/addresses", HttpMethod.Post, "/user/addresses", payload);
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("tests/user-subscriptions-get")]
    public async Task<IActionResult> UserSubscriptionsGet()
    {
        await SendUserAsync("GET /user/subscriptions", HttpMethod.Get, "/user/subscriptions");
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("tests/user-subscriptions-unsubscribe")]
    public async Task<IActionResult> UserSubscriptionsUnsubscribe(Guid subscriptionId)
    {
        if (subscriptionId == Guid.Empty)
        {
            StoreResult("POST /user/subscriptions/{id}/unsubscribe", "Enter a subscription id first.");
            return RedirectToAction(nameof(Index));
        }

        await SendUserAsync(
            "POST /user/subscriptions/{id}/unsubscribe",
            HttpMethod.Post,
            $"/user/subscriptions/{subscriptionId}/unsubscribe");
        return RedirectToAction(nameof(Index));
    }

    [HttpPost("auth/clear")]
    public IActionResult ClearTokens()
    {
        HttpContext.Session.Clear();
        return RedirectToAction(nameof(Index));
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }

    private TestDashboardViewModel BuildViewModel()
    {
        return new TestDashboardViewModel
        {
            Options = GetOptions(),
            UserAccessToken = HttpContext.Session.GetString(UserAccessTokenKey),
            UserRefreshToken = HttpContext.Session.GetString(UserRefreshTokenKey),
            ServiceAccessToken = HttpContext.Session.GetString(ServiceAccessTokenKey),
            LastResponseTitle = HttpContext.Session.GetString(LastTitleKey),
            LastResponseBody = HttpContext.Session.GetString(LastBodyKey)
        };
    }

    private MemberCenterTestOptions GetOptions()
    {
        var options = new MemberCenterTestOptions();
        _configuration.GetSection("MemberCenter").Bind(options);
        return options;
    }

    private async Task ExchangeUserTokenAsync(string title, Dictionary<string, string?> form)
    {
        var response = await PostFormAsync("/oauth/token", form);
        var body = await response.Content.ReadAsStringAsync();
        if (response.IsSuccessStatusCode)
        {
            SaveToken(body, UserAccessTokenKey, UserRefreshTokenKey);
        }

        StoreResult(title, FormatResponse(response, body));
    }

    private async Task<HttpResponseMessage> PostFormAsync(string path, Dictionary<string, string?> form)
    {
        var client = _httpClientFactory.CreateClient();
        var values = form
            .Where(x => !string.IsNullOrWhiteSpace(x.Value))
            .ToDictionary(x => x.Key, x => x.Value!);
        return await client.PostAsync($"{TrimSlash(GetOptions().ApiBaseUrl)}{path}", new FormUrlEncodedContent(values));
    }

    private async Task SendUserAsync(string title, HttpMethod method, string path, object? payload = null)
    {
        await SendApiAsync(title, method, path, HttpContext.Session.GetString(UserAccessTokenKey), payload);
    }

    private async Task SendServiceAsync(string title, HttpMethod method, string path, object? payload = null)
    {
        await SendApiAsync(title, method, path, HttpContext.Session.GetString(ServiceAccessTokenKey), payload);
    }

    private async Task SendApiAsync(string title, HttpMethod method, string path, string? token, object? payload = null)
    {
        if (string.IsNullOrWhiteSpace(token))
        {
            StoreResult(title, "Missing token. Login or request a service token first.");
            return;
        }

        var request = new HttpRequestMessage(method, $"{TrimSlash(GetOptions().ApiBaseUrl)}{path}");
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        if (payload is not null)
        {
            request.Content = new StringContent(JsonSerializer.Serialize(payload, JsonOptions), Encoding.UTF8, "application/json");
        }

        var client = _httpClientFactory.CreateClient();
        var response = await client.SendAsync(request);
        var body = await response.Content.ReadAsStringAsync();
        StoreResult(title, FormatResponse(response, body));
    }

    private void SaveToken(string body, string accessTokenKey, string? refreshTokenKey)
    {
        using var json = JsonDocument.Parse(body);
        if (json.RootElement.TryGetProperty("access_token", out var accessToken))
        {
            HttpContext.Session.SetString(accessTokenKey, accessToken.GetString() ?? string.Empty);
        }

        if (!string.IsNullOrWhiteSpace(refreshTokenKey)
            && json.RootElement.TryGetProperty("refresh_token", out var refreshToken))
        {
            HttpContext.Session.SetString(refreshTokenKey, refreshToken.GetString() ?? string.Empty);
        }
    }

    private void StoreResult(string title, string body)
    {
        HttpContext.Session.SetString(LastTitleKey, title);
        HttpContext.Session.SetString(LastBodyKey, PrettyJsonOrRaw(body));
    }

    private static string FormatResponse(HttpResponseMessage response, string body)
    {
        return $"HTTP {(int)response.StatusCode} {response.ReasonPhrase}\n\n{PrettyJsonOrRaw(body)}";
    }

    private static string PrettyJsonOrRaw(string body)
    {
        if (string.IsNullOrWhiteSpace(body))
        {
            return "(empty response)";
        }

        try
        {
            using var json = JsonDocument.Parse(body);
            return JsonSerializer.Serialize(json.RootElement, JsonOptions);
        }
        catch
        {
            return body;
        }
    }

    private static string BuildQuery(Dictionary<string, string?> values)
    {
        return string.Join("&", values
            .Where(x => !string.IsNullOrWhiteSpace(x.Value))
            .Select(x => $"{Uri.EscapeDataString(x.Key)}={Uri.EscapeDataString(x.Value!)}"));
    }

    private static string TrimSlash(string value) => value.TrimEnd('/');

    private static string Base64Url(byte[] bytes)
    {
        return Convert.ToBase64String(bytes)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }
}
