using System.Text.Json;
using Ory.Kratos.Client.Api;
using Ory.Kratos.Client.Model;
using Ory.Kratos.Client.Client;

namespace khmer_eid_backend.Integrations.Ory;

public class KratosIntegration
{
    private readonly FrontendApi _frontendApi;
    private readonly IdentityApi _identityApi;
    private readonly ILogger<KratosIntegration> _logger;

    public KratosIntegration(IConfiguration config, ILogger<KratosIntegration> logger)
    {
        var publicCfg = new Configuration { BasePath = config["Ory:Kratos:PublicUrl"]! };
        var adminCfg = new Configuration { BasePath = config["Ory:Kratos:AdminUrl"]! };

        _frontendApi = new FrontendApi(publicCfg);
        _identityApi = new IdentityApi(adminCfg);
        _logger = logger;
    }

    public async Task<dynamic> CreateOtpRegistrationFlowAsync(string phone)
    {
            var flow = await _frontendApi.CreateNativeRegistrationFlowAsync(returnSessionTokenExchangeCode: true);
            _logger.LogInformation("Started registration flow: {FlowId}", flow.Id);

            phone = phone.Trim();
            var method = new KratosUpdateRegistrationFlowWithCodeMethod(
                traits: new { phone = phone },
                method: "code"
            );

        try
        {
            var body = new KratosUpdateRegistrationFlowBody(method);
            _logger.LogInformation("Updating OTP registration flow for {Phone}, flow {FlowId}", phone, flow.Id);
            var updatedFlow = _frontendApi.UpdateRegistrationFlow(flow.Id, body);

            return flow;
        }
        catch (ApiException ex)
        {
            var res = JsonSerializer.Deserialize<JsonElement>(ex.ErrorContent.ToString()!);
            return ex.ErrorCode switch
            {
                400 => res.GetProperty("state").ToString() == "sent_email" ?
                    new
                    {
                        FlowId = res.GetProperty("id").GetString(),
                        State = res.GetProperty("state").GetString(),
                        ExpiredAt = res.GetProperty("expires_at").GetDateTime()
                    } : throw new Exception("Unhandled Kratos API exception: " + ex.Message),
                _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message),
            };
        }
    }

    public async Task<dynamic> CompleteOtpRegistrationFlowAsync(string flowId, string phone,string otp)
    {
        phone = phone.Trim();
        var method = new KratosUpdateRegistrationFlowWithCodeMethod(
            code: otp,
            traits: new { phone = phone},
            method: "code"
        );
        var body = new KratosUpdateRegistrationFlowBody(method);

        try
        {
            var result = await _frontendApi.UpdateRegistrationFlowAsync(flowId, body);

            _logger.LogInformation("Completed OTP registration flow for flow {FlowId}", flowId);
            return new
            {
                accessToken = result.SessionToken,
                expiredAt = result.Session?.ExpiresAt
            };
        }
        catch (ApiException ex)
        {
            var res = JsonSerializer.Deserialize<JsonElement>(ex.ErrorContent.ToString()!);
            return ex.ErrorCode switch
            {
                400 => res.GetProperty("ui").GetProperty("messages").EnumerateArray().First().GetProperty("text").GetString()!,
                404 => throw new Exception("Registration flow not found."),
                _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message),
            };
        }
    }

    public async Task<dynamic> CreateOtpLoginFlowAsync(string phone)
    {
        var flow = await _frontendApi.CreateNativeLoginFlowAsync(returnSessionTokenExchangeCode: true);
        _logger.LogInformation("Started login flow: {FlowId}", flow.Id);

        phone = phone.Trim();
        var method = new KratosUpdateLoginFlowWithCodeMethod(
            method: "code",
            csrfToken: "dfdfdfde",
            identifier: phone
        );

        try
        {
            var body = new KratosUpdateLoginFlowBody(method);
            _logger.LogInformation("Updating OTP registration flow for {Phone}, flow {FlowId}", phone, flow.Id);
            var updatedFlow = await _frontendApi.UpdateLoginFlowAsync(flow.Id, body);

            return flow;
        }
        catch (ApiException ex)
        {
            var res = JsonSerializer.Deserialize<JsonElement>(ex.ErrorContent.ToString()!);
            return ex.ErrorCode switch
            {
                400 => res.GetProperty("state").ToString() == "sent_email" ?
                    new
                    {
                        FlowId = res.GetProperty("id").GetString(),
                        State = res.GetProperty("state").GetString(),
                        ExpiredAt = res.GetProperty("expires_at").GetDateTime()
                    } : throw new Exception("Unhandled Kratos API exception: " + ex.Message),
                _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message),
            };
        }
    }

    public async Task<dynamic> CompleteOtpLoginFlowAsync(string flowId, string phone, string otp)
    {
        phone = "+" + phone.Trim();
        var method = new KratosUpdateLoginFlowWithCodeMethod(
            code: otp,
            method: "code",
            csrfToken: "dfdfdfde",
            identifier: phone
        );
        var body = new KratosUpdateLoginFlowBody(method);

        try
        {
            var result = await _frontendApi.UpdateLoginFlowAsync(flowId, body);

            _logger.LogInformation("Completed OTP login flow for flow {FlowId}", flowId);
            return new
            {
                accessToken = result.SessionToken,
                expiredAt = result.Session?.ExpiresAt
            };
        }
        catch (ApiException ex)
        {
            var res = JsonSerializer.Deserialize<JsonElement>(ex.ErrorContent.ToString()!);
            return ex.ErrorCode switch
            {
                400 => res,
                404 => throw new Exception("Login flow not found."),
                _ => throw new Exception("Unhandled Kratos API exception: " + ex.Message),
            };
        }
    }

    public async Task<dynamic> Logout(string sessionToken)
    {
        var body = new KratosPerformNativeLogoutBody(sessionToken: sessionToken);
        await _frontendApi.PerformNativeLogoutAsync(body);
        _logger.LogInformation("Logged out session with token {SessionToken}", sessionToken);
        return new { Message = "Logged out successfully." };
    }

    public async Task<dynamic> GetMe(string sessionToken)
    {
        var session = await _frontendApi.ToSessionAsync(xSessionToken: sessionToken);
        _logger.LogInformation("Fetched session for identity {IdentityId}", session.Identity.Id);
        return session;
    }

    public async Task<KratosSuccessfulNativeRegistration> PasswordRegistrationFlowAsync(string flowId, string phone)
    {
        var method = new KratosUpdateRegistrationFlowWithPasswordMethod(
            password: "add3ae4d8ae8",
            traits: new { phone = phone, identifier = phone },
            method: "password"
        );
        var body = new KratosUpdateRegistrationFlowBody(method);
        var result = await _frontendApi.UpdateRegistrationFlowAsync(flowId, body);

        Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
        _logger.LogInformation("Completed registration flow for {Phone}", phone);
        return result;
    }
}
