using Microsoft.AspNetCore.Mvc;
using Tiku.Api.Controllers;
using Tiku.Application.Assets;
using Tiku.Application.Auth;
using Tiku.Application.Backoffice;
using Tiku.Application.Commerce;
using Tiku.Application.Content;
using Tiku.Application.Growth;
using Tiku.Application.Jobs;
using Tiku.Application.Learning;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.PlatformBilling;
using Tiku.Application.Points;
using Tiku.Application.Profile;
using Tiku.Application.QuestionBanks;
using Tiku.Application.Scoreline;
using Tiku.Application.Security;
using Tiku.Application.Storage;
using Tiku.Application.Tenancy;
using Tiku.Application.TenantAdmin;

namespace Tiku.Api.Middleware;

public sealed class ExceptionHandlingMiddleware(
    RequestDelegate next,
    ILogger<ExceptionHandlingMiddleware> logger,
    IHostEnvironment environment)
{
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception exception)
        {
            if (exception is PlatformApprovalException approvalException)
            {
                var status = approvalException.Code switch
                {
                    "approval_request_not_found" or "approval_policy_not_found" => StatusCodes.Status404NotFound,
                    "platform_access_denied" or "approval_business_permission_required" or "approval_cancel_denied" =>
                        StatusCodes.Status403Forbidden,
                    "approval_request_not_pending" or "approval_request_expired" or "approval_maker_checker_required"
                        or "idempotency_conflict" => StatusCodes.Status409Conflict,
                    _ => StatusCodes.Status400BadRequest
                };
                await WriteProblemAsync(context, approvalException.Message, status, approvalException.Code);
                return;
            }

            if (exception is AuthorizationSecurityUnavailableException)
            {
                await WriteProblemAsync(
                    context,
                    "Authentication security service is unavailable.",
                    StatusCodes.Status503ServiceUnavailable,
                    "auth_security_unavailable");
                return;
            }

            if (exception is AuthException authException)
            {
                await WriteAuthProblemAsync(context, authException);
                return;
            }

            if (exception is OwnerActivationException ownerActivationException)
            {
                var status = ownerActivationException.Code is "owner_activation_consumed"
                    ? StatusCodes.Status409Conflict
                    : StatusCodes.Status400BadRequest;
                await WriteProblemAsync(context, ownerActivationException.Message, status,
                    ownerActivationException.Code);
                return;
            }

            if (exception is TenantNotFoundException)
            {
                await WriteProblemAsync(
                    context,
                    "Tenant was not found.",
                    StatusCodes.Status404NotFound,
                    "tenant_not_found");
                return;
            }

            if (exception is TenantLifecycleException lifecycleException)
            {
                var status = lifecycleException.Code switch
                {
                    "tenant_not_found" => StatusCodes.Status404NotFound,
                    "tenant_export_not_ready" => StatusCodes.Status409Conflict,
                    "tenant_archive_blocked" or "tenant_not_archived" or
                        "tenant_owner_target_not_active_member"
                        or "tenant_owner_unchanged" => StatusCodes.Status409Conflict,
                    _ => StatusCodes.Status400BadRequest
                };
                await WriteProblemAsync(context, lifecycleException.Message, status, lifecycleException.Code);
                return;
            }

            if (exception is BrowserOriginException)
            {
                await WriteProblemAsync(context, exception.Message, StatusCodes.Status403Forbidden,
                    "browser_origin_rejected");
                return;
            }

            if (exception is FeatureAccessException featureAccessException)
            {
                await WriteProblemAsync(
                    context,
                    featureAccessException.Message,
                    featureAccessException.Code == "feature_quota_exhausted"
                        ? StatusCodes.Status409Conflict
                        : StatusCodes.Status403Forbidden,
                    featureAccessException.Code);
                return;
            }

            if (exception is BackgroundJobException backgroundJobException)
            {
                var status = backgroundJobException.Code switch
                {
                    "background_job_not_found" => StatusCodes.Status404NotFound,
                    "background_job_not_cancellable" or "background_job_not_retryable" => StatusCodes.Status409Conflict,
                    _ => StatusCodes.Status400BadRequest
                };
                await WriteProblemAsync(
                    context,
                    backgroundJobException.Message,
                    status,
                    backgroundJobException.Code);
                return;
            }

            if (exception is TenantContextConflictException)
            {
                await WriteProblemAsync(
                    context,
                    exception.Message,
                    StatusCodes.Status403Forbidden,
                    "tenant_context_conflict");
                return;
            }

            if (exception is TenantFrontendConfigException frontendConfigException)
            {
                var status = frontendConfigException.Code switch
                {
                    "tenant_not_found" or "frontend_config_not_found" => StatusCodes.Status404NotFound,
                    "frontend_config_version_conflict" => StatusCodes.Status409Conflict,
                    _ => StatusCodes.Status400BadRequest
                };
                await WriteProblemAsync(
                    context,
                    frontendConfigException.Message,
                    status,
                    frontendConfigException.Code);
                return;
            }

            if (exception is PublicQuestionAccessDeniedException publicQuestionAccessDeniedException)
            {
                await WriteProblemAsync(
                    context,
                    publicQuestionAccessDeniedException.Message,
                    StatusCodes.Status403Forbidden,
                    publicQuestionAccessDeniedException.Code);
                return;
            }

            if (exception is QuestionLocatorException questionLocatorException)
            {
                await WriteProblemAsync(
                    context,
                    questionLocatorException.Message,
                    StatusCodes.Status404NotFound,
                    questionLocatorException.Code);
                return;
            }

            if (exception is RequiredFieldException)
            {
                await WriteProblemAsync(
                    context,
                    exception.Message,
                    StatusCodes.Status400BadRequest,
                    "required_field");
                return;
            }

            if (exception is ContentNavigationNotFoundException)
            {
                await WriteProblemAsync(
                    context,
                    exception.Message,
                    StatusCodes.Status404NotFound,
                    "content_navigation_not_found");
                return;
            }

            if (exception is QuestionBankRequiredFieldException)
            {
                await WriteProblemAsync(
                    context,
                    exception.Message,
                    StatusCodes.Status400BadRequest,
                    "required_field");
                return;
            }

            if (exception is QuestionBankNotFoundException)
            {
                await WriteProblemAsync(
                    context,
                    exception.Message,
                    StatusCodes.Status404NotFound,
                    "question_not_found");
                return;
            }

            if (exception is AssetAccessException assetAccessException)
            {
                await WriteProblemAsync(
                    context,
                    assetAccessException.Message,
                    AssetAccessStatusCode(assetAccessException.Code),
                    assetAccessException.Code.ToLowerInvariant());
                return;
            }

            if (exception is AssetManagementException assetManagementException)
            {
                await WriteProblemAsync(
                    context,
                    assetManagementException.Message,
                    AssetManagementStatusCode(assetManagementException.Code),
                    assetManagementException.Code);
                return;
            }

            if (exception is VideoPlaybackException videoPlaybackException)
            {
                await WriteProblemAsync(
                    context,
                    videoPlaybackException.Message,
                    VideoPlaybackStatusCode(videoPlaybackException.Code),
                    videoPlaybackException.Code);
                return;
            }

            if (exception is ContentManagementException contentManagementException)
            {
                await WriteProblemAsync(
                    context,
                    contentManagementException.Message,
                    ContentManagementStatusCode(contentManagementException.Code),
                    contentManagementException.Code);
                return;
            }

            if (exception is LearningValidationException learningValidationException)
            {
                await WriteProblemAsync(
                    context,
                    learningValidationException.Message,
                    LearningValidationStatusCode(learningValidationException.Code),
                    learningValidationException.Code);
                return;
            }

            if (exception is LearningResourceNotFoundException learningResourceNotFoundException)
            {
                await WriteProblemAsync(
                    context,
                    learningResourceNotFoundException.Message,
                    StatusCodes.Status404NotFound,
                    learningResourceNotFoundException.Code);
                return;
            }

            if (exception is LearningAccessDeniedException)
            {
                await WriteProblemAsync(
                    context,
                    exception.Message,
                    StatusCodes.Status403Forbidden,
                    "learning_access_denied");
                return;
            }

            if (exception is ScorelineQueryException scorelineQueryException)
            {
                await WriteProblemAsync(
                    context,
                    scorelineQueryException.Message,
                    StatusCodes.Status400BadRequest,
                    scorelineQueryException.Code);
                return;
            }

            if (exception is ProfileException profileException)
            {
                await WriteProblemAsync(
                    context,
                    profileException.Message,
                    ProfileStatusCode(profileException.Code),
                    profileException.Code);
                return;
            }

            if (exception is TenantAdminDirectException tenantAdminDirectException)
            {
                await WriteProblemAsync(
                    context,
                    tenantAdminDirectException.Message,
                    TenantAdminDirectStatusCode(tenantAdminDirectException.Code),
                    tenantAdminDirectException.Code);
                return;
            }

            if (exception is BackofficeException backofficeException)
            {
                await WriteProblemAsync(
                    context,
                    backofficeException.Message,
                    BackofficeStatusCode(backofficeException.Code),
                    backofficeException.Code);
                return;
            }

            if (exception is PlatformAdminException platformAdminException)
            {
                await WriteProblemAsync(
                    context,
                    platformAdminException.Message,
                    PlatformAdminStatusCode(platformAdminException.Code),
                    platformAdminException.Code);
                return;
            }

            if (exception is PlatformBillingException platformBillingException)
            {
                await WriteProblemAsync(
                    context,
                    platformBillingException.Message,
                    PlatformBillingStatusCode(platformBillingException.Code),
                    platformBillingException.Code);
                return;
            }

            if (exception is CommerceException commerceException)
            {
                await WriteProblemAsync(
                    context,
                    commerceException.Message,
                    CommerceStatusCode(commerceException.Code),
                    commerceException.Code);
                return;
            }

            if (exception is PointException pointException)
            {
                await WriteProblemAsync(
                    context,
                    pointException.Message,
                    PointStatusCode(pointException.Code),
                    pointException.Code);
                return;
            }

            if (exception is ReferralException referralException)
            {
                await WriteProblemAsync(
                    context,
                    referralException.Message,
                    ReferralStatusCode(referralException.Code),
                    referralException.Code);
                return;
            }

            if (exception is CrmException crmException)
            {
                await WriteProblemAsync(
                    context,
                    crmException.Message,
                    CrmStatusCode(crmException.Code),
                    crmException.Code);
                return;
            }

            if (exception is CommissionException commissionException)
            {
                await WriteProblemAsync(
                    context,
                    commissionException.Message,
                    CommissionStatusCode(commissionException.Code),
                    commissionException.Code);
                return;
            }

            if (exception is PaymentProviderException paymentProviderException)
            {
                await WriteProblemAsync(
                    context,
                    paymentProviderException.Message,
                    CommerceStatusCode(paymentProviderException.Code),
                    paymentProviderException.Code);
                return;
            }

            if (exception is TenantExternalProviderException externalProviderException)
            {
                await WriteProblemAsync(
                    context,
                    externalProviderException.Message,
                    StatusCodes.Status400BadRequest,
                    externalProviderException.Code);
                return;
            }

            if (exception is PlatformCapabilityException platformCapabilityException)
            {
                await WriteProblemAsync(
                    context,
                    platformCapabilityException.Message,
                    StatusCodes.Status400BadRequest,
                    platformCapabilityException.Code);
                return;
            }

            if (exception is ObjectStorageException storageException)
            {
                await WriteProblemAsync(
                    context,
                    storageException.Message,
                    storageException is ObjectStorageNotConfiguredException
                        ? StatusCodes.Status503ServiceUnavailable
                        : StatusCodes.Status400BadRequest,
                    storageException.Code.ToLowerInvariant());
                return;
            }

            logger.LogError(exception, "Unhandled API exception");

            var problem = new ProblemDetails
            {
                Title = "An unexpected error occurred.",
                Status = StatusCodes.Status500InternalServerError,
                Detail = environment.IsDevelopment() ? exception.Message : null,
                Instance = context.Request.Path
            };

            problem.Extensions["traceId"] = context.TraceIdentifier;
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await context.Response.WriteAsJsonAsync(problem);
        }
    }

    private static async Task WriteProblemAsync(
        HttpContext context,
        string title,
        int status,
        string code)
    {
        var problem = new ProblemDetails
        {
            Title = title,
            Status = status,
            Instance = context.Request.Path
        };

        problem.Extensions["code"] = code;
        problem.Extensions["traceId"] = context.TraceIdentifier;
        context.Response.StatusCode = status;
        await context.Response.WriteAsJsonAsync(problem);
    }

    private static async Task WriteAuthProblemAsync(HttpContext context, AuthException exception)
    {
        var status = exception.Code switch
        {
            "tenant_access_denied" => StatusCodes.Status403Forbidden,
            "auth_session_not_found" => StatusCodes.Status404NotFound,
            "current_auth_session_cannot_be_revoked" => StatusCodes.Status409Conflict,
            "sms_rate_limited" => StatusCodes.Status429TooManyRequests,
            "auth_provider_not_configured" => StatusCodes.Status503ServiceUnavailable,
            "auth_security_unavailable" => StatusCodes.Status503ServiceUnavailable,
            "session_revoked" => StatusCodes.Status401Unauthorized,
            _ => StatusCodes.Status401Unauthorized
        };
        var problem = new ProblemDetails
        {
            Title = exception.Message,
            Status = status,
            Instance = context.Request.Path
        };

        problem.Extensions["code"] = exception.Code;
        problem.Extensions["traceId"] = context.TraceIdentifier;
        context.Response.StatusCode = status;
        await context.Response.WriteAsJsonAsync(problem);
    }

    private static int AssetAccessStatusCode(string code)
    {
        return code switch
        {
            "ASSET_NOT_FOUND" => StatusCodes.Status404NotFound,
            "AUTH_REQUIRED" => StatusCodes.Status401Unauthorized,
            "ASSET_HIDDEN" or "ASSET_MEMBERSHIP_REQUIRED" or "ASSET_SVIP_REQUIRED" => StatusCodes.Status403Forbidden,
            "ASSET_UPLOAD_NOT_VERIFIED" or "ASSET_SECURITY_SCAN_NOT_PASSED" => StatusCodes.Status409Conflict,
            "ASSET_PREVIEW_NOT_SUPPORTED" => StatusCodes.Status400BadRequest,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int LearningValidationStatusCode(string code)
    {
        return code switch
        {
            "no_practice_questions" or "practice_session_empty" or
                "idempotency_conflict" or "practice_session_version_conflict" or
                "practice_client_sequence_conflict" or "practice_answer_conflict" or
                "practice_submission_conflict" or "practice_session_not_active" or
                "practice_session_expired" => StatusCodes.Status409Conflict,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int AssetManagementStatusCode(string code)
    {
        return code switch
        {
            "asset_not_found" or "import_job_not_found" => StatusCodes.Status404NotFound,
            "asset_upload_missing" => StatusCodes.Status409Conflict,
            "tenant_content_access_denied" => StatusCodes.Status403Forbidden,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int ContentManagementStatusCode(string code)
    {
        return code switch
        {
            "entry_not_found" or "node_not_found" or "collection_not_found" or "question_not_found" or
                "import_type_invalid" => StatusCodes.Status404NotFound,
            "tenant_content_access_denied" => StatusCodes.Status403Forbidden,
            _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int ProfileStatusCode(string code)
    {
        return code switch
        {
            "profile_access_denied" => StatusCodes.Status403Forbidden,
            "profile_user_not_found" or "check_in_task_not_found" => StatusCodes.Status404NotFound,
            "region_not_found" or "school_not_found" or "major_not_found" => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int VideoPlaybackStatusCode(string code)
    {
        return code switch
        {
            "video_access_denied" => StatusCodes.Status403Forbidden,
            "video_not_found" or "question_video_not_found" => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int TenantAdminDirectStatusCode(string code)
    {
        return code switch
        {
            "tenant_admin_access_denied" => StatusCodes.Status403Forbidden,
            "class_not_found" or "class_member_not_found" or "student_not_found" or "user_not_found" => StatusCodes
                .Status404NotFound,
            "tenant_member_not_found" => StatusCodes.Status404NotFound,
            _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int CommerceStatusCode(string code)
    {
        return code switch
        {
            "commerce_access_denied" or "tenant_access_denied" => StatusCodes.Status403Forbidden,
            "tenant_admin_access_denied" => StatusCodes.Status403Forbidden,
            "order_not_found" or "svip_plan_not_found" or "region_not_found" or "activation_code_not_found" or
                "coupon_not_found" or "coupon_redemption_not_found" => StatusCodes.Status404NotFound,
            "payment_provider_not_configured" or "payment_secret_not_configured" => StatusCodes
                .Status503ServiceUnavailable,
            "order_status_invalid" or "activation_code_used" or "payment_amount_mismatch" or
                "coupon_usage_limit_reached" or "coupon_redemption_status_invalid" => StatusCodes.Status409Conflict,
            _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int BackofficeStatusCode(string code)
    {
        return code switch
        {
            "platform_access_denied" or "tenant_access_denied" or "capability_not_available" => StatusCodes
                .Status403Forbidden,
            _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int PlatformAdminStatusCode(string code)
    {
        return code switch
        {
            "platform_access_denied" => StatusCodes.Status403Forbidden,
            "tenant_slug_exists" or "idempotency_conflict" or "owner_activation_already_issued" or
                "owner_already_activated" => StatusCodes.Status409Conflict,
            _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int PlatformBillingStatusCode(string code)
    {
        return code switch
        {
            "tenant_not_found" => StatusCodes.Status404NotFound,
            "platform_billing_public_url_missing" or "payment_provider_not_configured" or
                "payment_secret_not_configured" => StatusCodes.Status503ServiceUnavailable,
            "saas_offering_version_immutable" or "saas_offering_version_status_invalid" or
                "platform_billing_quote_expired" or "platform_billing_order_status_invalid" or
                "platform_billing_payment_status_invalid" or "platform_billing_payment_amount_mismatch" or
                "platform_billing_order_not_cancellable" or "platform_billing_payment_not_refundable" or
                "platform_billing_refund_amount_invalid" or "platform_billing_refund_not_retryable" or
                "platform_billing_refund_status_invalid" or "tenant_saas_subscription_status_invalid" or
                "tenant_saas_subscription_exists" or "idempotency_conflict" =>
                StatusCodes.Status409Conflict,
            _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int PointStatusCode(string code)
    {
        return code switch
        {
            "point_access_denied" or "tenant_access_denied" => StatusCodes.Status403Forbidden,
            "point_task_not_found" or "point_exchange_item_not_found" => StatusCodes.Status404NotFound,
            "point_task_claim_limit_reached" or "insufficient_points" or "point_exchange_item_sold_out" =>
                StatusCodes.Status409Conflict,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int ReferralStatusCode(string code)
    {
        return code switch
        {
            "referral_access_denied" or "tenant_access_denied" => StatusCodes.Status403Forbidden,
            "referral_code_not_found" => StatusCodes.Status404NotFound,
            "referral_lead_protected" or "self_referral_not_allowed" => StatusCodes.Status409Conflict,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int CrmStatusCode(string code)
    {
        return code switch
        {
            "crm_access_denied" => StatusCodes.Status403Forbidden,
            "crm_queue_not_found" => StatusCodes.Status404NotFound,
            "crm_queue_status_invalid" => StatusCodes.Status409Conflict,
            _ => StatusCodes.Status400BadRequest
        };
    }

    private static int CommissionStatusCode(string code)
    {
        return code switch
        {
            "commission_access_denied" => StatusCodes.Status403Forbidden,
            "commission_member_not_found" or "commission_settlement_not_found" or "commission_proof_not_found" =>
                StatusCodes.Status404NotFound,
            "commission_no_unsettled_sources" or "commission_below_minimum" or "commission_status_invalid" or
                "commission_proof_closed" => StatusCodes.Status409Conflict,
            _ => StatusCodes.Status400BadRequest
        };
    }
}