using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Tiku.Api.Controllers;

namespace Tiku.IntegrationTests;

public sealed class ArchitectureBoundaryTests
{
    [Fact]
    public void Api_controllers_depend_on_application_contracts_not_infrastructure()
    {
        var root = FindRepositoryRoot();
        var violations = Directory.EnumerateFiles(
                Path.Combine(root, "Tiku.Api", "Controllers"),
                "*.cs",
                SearchOption.AllDirectories)
            .SelectMany(path => File.ReadLines(path)
                .Select((line, index) => new { path, line, lineNumber = index + 1 }))
            .Where(candidate =>
                candidate.line.Contains("using Tiku.Infrastructure", StringComparison.Ordinal) ||
                candidate.line.Contains("TikuDbContext", StringComparison.Ordinal))
            .Select(candidate => $"{Path.GetRelativePath(root, candidate.path)}:{candidate.lineNumber}")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"API controllers crossed the Application boundary:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    [Fact]
    public void Service_classes_do_not_exceed_the_hard_size_limit_or_grow_legacy_debt()
    {
        var root = FindRepositoryRoot();
        var legacyLineBudgets = new Dictionary<string, int>(StringComparer.Ordinal)
        {
            ["TenantAdminDirectService"] = 3190,
            ["DirectContentService"] = 2015,
            ["CommerceAdminService"] = 1842,
            ["LearningActivityService"] = 1781,
            ["PlatformAdminService"] = 1719,
            ["ContentManagementService"] = 1096,
            ["PlatformQuestionBankService"] = 1033,
            ["CommerceService"] = 983,
            ["AssetManagementService"] = 948,
            ["AuthService"] = 876,
            ["ReferralService"] = 849,
            ["PlatformTenantCapabilitiesService"] = 805
        };

        var serviceGroups = Directory.EnumerateFiles(
                Path.Combine(root, "Tiku.Infrastructure"),
                "*Service*.cs",
                SearchOption.AllDirectories)
            .Where(path => !path.Contains(
                $"{Path.DirectorySeparatorChar}Migrations{Path.DirectorySeparatorChar}",
                StringComparison.Ordinal))
            .GroupBy(path => Path.GetFileNameWithoutExtension(path).Split('.')[0], StringComparer.Ordinal)
            .Select(group => new
            {
                Service = group.Key,
                Lines = group.Sum(path => File.ReadLines(path).Count()),
                Files = group.Count()
            })
            .ToArray();

        var violations = serviceGroups
            .Where(item => legacyLineBudgets.TryGetValue(item.Service, out var budget)
                ? item.Lines > budget
                : item.Lines > 800)
            .Select(item => legacyLineBudgets.TryGetValue(item.Service, out var budget)
                ? $"{item.Service} ({item.Lines} lines across {item.Files} files; legacy budget {budget})"
                : $"{item.Service} ({item.Lines} lines across {item.Files} files; hard limit 800)")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"Service classes exceeded the hard limit or grew legacy debt:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    [Fact]
    public void Background_jobs_are_dispatched_by_module_registered_handlers()
    {
        var root = FindRepositoryRoot();
        var infrastructureRoot = Path.Combine(root, "Tiku.Infrastructure");
        var sourceFiles = Directory.EnumerateFiles(infrastructureRoot, "*.cs", SearchOption.AllDirectories)
            .Where(path => !path.Contains(
                $"{Path.DirectorySeparatorChar}Persistence{Path.DirectorySeparatorChar}Migrations{Path.DirectorySeparatorChar}",
                StringComparison.Ordinal))
            .ToArray();
        var handlerFiles = sourceFiles
            .Where(path => File.ReadAllText(path).Contains(": IBackgroundJobHandler", StringComparison.Ordinal))
            .ToArray();
        var expectedJobTypes = new[]
        {
            "content_export",
            "content_import",
            "asset_security_scan",
            "tenant_export",
            "statistics_aggregation",
            "commerce_reconciliation",
            "tenant_domain_recheck"
        };

        Assert.Equal(expectedJobTypes.Length, handlerFiles.Length);
        foreach (var jobType in expectedJobTypes)
            Assert.Single(handlerFiles, path =>
                File.ReadAllText(path).Contains($"JobType => \"{jobType}\"", StringComparison.Ordinal));

        var moduleRegistrations = Directory
            .EnumerateFiles(Path.Combine(infrastructureRoot, "Modules"), "*.cs", SearchOption.AllDirectories)
            .Sum(path => File.ReadLines(path).Count(line =>
                line.Contains("AddScoped<IBackgroundJobHandler", StringComparison.Ordinal)));
        Assert.Equal(expectedJobTypes.Length, moduleRegistrations);

        var jobsSource = string.Join(Environment.NewLine, Directory
            .EnumerateFiles(Path.Combine(infrastructureRoot, "Jobs"), "*.cs", SearchOption.AllDirectories)
            .Select(File.ReadAllText));
        Assert.DoesNotContain("Dictionary<string, Func<Task<JsonElement>>>", jobsSource, StringComparison.Ordinal);
    }

    [Fact]
    public void Production_authorization_does_not_depend_on_legacy_role_claims()
    {
        var root = FindRepositoryRoot();
        var authorizationFiles = Directory
            .EnumerateFiles(Path.Combine(root, "Tiku.Api", "Security"), "*.cs", SearchOption.AllDirectories)
            .Append(Path.Combine(root, "Tiku.Api", "Configuration", "AuthenticationExtensions.cs"));
        var forbidden = new[]
        {
            "TikuClaimTypes.TenantRole",
            "TenantRoleAuthorization",
            "PrimaryRole"
        };

        AssertNoForbiddenSymbols(
            root,
            authorizationFiles,
            forbidden,
            "Production authorization still depends on a legacy role claim or primary role");
    }

    [Fact]
    public void Backoffice_controllers_do_not_construct_platform_access_flags()
    {
        var root = FindRepositoryRoot();
        var controllerFiles = Directory.EnumerateFiles(
            Path.Combine(root, "Tiku.Api", "Controllers"),
            "*Backoffice*Controller.cs",
            SearchOption.AllDirectories);
        var forbidden = new[]
        {
            "new BackofficeActor(",
            "IsPlatformAdmin(",
            "IsPlatform ="
        };

        AssertNoForbiddenSymbols(
            root,
            controllerFiles,
            forbidden,
            "Backoffice controllers must use the resolved access context instead of constructing platform access flags");
    }

    [Fact]
    public void BackendAuthorizationDoesNotUseMembershipBusinessRoles()
    {
        var root = FindRepositoryRoot();
        var files = new[]
        {
            Path.Combine(root, "Tiku.Infrastructure", "Growth", "CommissionService.cs"),
            Path.Combine(root, "Tiku.Infrastructure", "Growth", "ReferralService.cs"),
            Path.Combine(root, "Tiku.Api", "Controllers", "TenantAdminDirectController.cs")
        };

        AssertNoForbiddenSymbols(
            root,
            files,
            new[]
            {
                "item.Role == TenantRole.TenantOwner",
                "item.Role == TenantRole.TenantAdmin",
                "TenantRole Role = TenantRole.TenantAdmin",
                "new TenantAdminActor("
            },
            "Backend authorization must use database role permissions rather than membership business roles or fabricated admin actors");
    }

    [Fact]
    public void Auth_sessions_are_accessed_only_through_the_session_store()
    {
        var root = FindRepositoryRoot();
        var sourceRoots = new[] { "Tiku.Api", "Tiku.Application", "Tiku.Infrastructure" };
        var allowedFiles = new[]
        {
            "TikuDbContext.cs",
            "AuthSessionStore.cs",
            "SessionStore.cs"
        };

        var files = sourceRoots
            .SelectMany(directory => Directory.EnumerateFiles(
                Path.Combine(root, directory),
                "*.cs",
                SearchOption.AllDirectories))
            .Where(path => !path.Contains(
                $"{Path.DirectorySeparatorChar}Persistence{Path.DirectorySeparatorChar}Migrations{Path.DirectorySeparatorChar}",
                StringComparison.Ordinal))
            .Where(path => !path.Contains(
                $"{Path.DirectorySeparatorChar}Persistence{Path.DirectorySeparatorChar}Configurations{Path.DirectorySeparatorChar}",
                StringComparison.Ordinal))
            .Where(path =>
                !allowedFiles.Contains(Path.GetFileName(path), StringComparer.Ordinal) &&
                !Path.GetFileName(path).StartsWith("TikuDbContext.", StringComparison.Ordinal));

        AssertNoForbiddenSymbols(
            root,
            files,
            new[] { ".AuthSessions", "Set<AuthSession>" },
            "AuthSession DbSet access must be encapsulated by IAuthSessionStore");
    }

    [Fact]
    public void Api_uses_an_authenticated_fallback_policy()
    {
        var root = FindRepositoryRoot();
        var authenticationRegistration = File.ReadAllText(
            Path.Combine(root, "Tiku.Api", "Configuration", "AuthenticationExtensions.cs"));

        Assert.Contains("FallbackPolicy", authenticationRegistration, StringComparison.Ordinal);
        Assert.Contains("RequireAuthenticatedUser()", authenticationRegistration, StringComparison.Ordinal);
    }

    [Fact]
    public void Every_controller_action_declares_authorization_or_anonymous_access()
    {
        var controllerAssembly = typeof(AuthController).Assembly;
        var violations = controllerAssembly
            .GetTypes()
            .Where(type => !type.IsAbstract && typeof(ControllerBase).IsAssignableFrom(type))
            .SelectMany(type => type
                .GetMethods(BindingFlags.Instance |
                            BindingFlags.Public |
                            BindingFlags.DeclaredOnly)
                .Where(method => method
                    .GetCustomAttributes(true)
                    .OfType<HttpMethodAttribute>()
                    .Any())
                .Select(method => new
                {
                    Controller = type,
                    Action = method,
                    Metadata = type.GetCustomAttributes(true)
                        .Concat(method.GetCustomAttributes(true))
                }))
            .Where(candidate => !candidate.Metadata.Any(attribute =>
                attribute is IAuthorizeData or
                    IAllowAnonymous))
            .Select(candidate => $"{candidate.Controller.FullName}.{candidate.Action.Name}")
            .Order(StringComparer.Ordinal)
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"Controller actions without explicit authorization metadata were found:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    [Fact]
    public void Business_code_does_not_bypass_tenant_query_boundaries()
    {
        var root = FindRepositoryRoot();
        var sourceRoots = new[] { "Tiku.Api", "Tiku.Application", "Tiku.Infrastructure" };
        var forbidden = new[]
        {
            "IgnoreQueryFilters(",
            "FromSql(",
            "FromSqlRaw(",
            "FromSqlInterpolated(",
            "ExecuteSql(",
            "ExecuteSqlRaw(",
            "ExecuteSqlInterpolated(",
            "new NpgsqlCommand("
        };

        var violations = sourceRoots
            .SelectMany(directory => Directory.EnumerateFiles(
                Path.Combine(root, directory),
                "*.cs",
                SearchOption.AllDirectories))
            .Where(path => !path.Contains(
                $"{Path.DirectorySeparatorChar}Persistence{Path.DirectorySeparatorChar}Migrations{Path.DirectorySeparatorChar}",
                StringComparison.Ordinal))
            .SelectMany(path => File.ReadLines(path)
                .Select((line, index) => new { path, line, lineNumber = index + 1 }))
            .Where(candidate => forbidden.Any(symbol =>
                candidate.line.Contains(symbol, StringComparison.Ordinal)))
            .Select(candidate => $"{Path.GetRelativePath(root, candidate.path)}:{candidate.lineNumber}")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"Forbidden tenant-boundary bypasses were found:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    [Fact]
    public void Business_code_does_not_depend_on_supabase_or_removed_provider_models()
    {
        var root = FindRepositoryRoot();
        var sourceRoots = new[] { "Tiku.Api", "Tiku.Application", "Tiku.Domain", "Tiku.Infrastructure" };
        var forbidden = new[]
        {
            "Supabase",
            "supabase_storage",
            "SUPABASE_",
            "TenantAuthProvider",
            "TenantPaymentAccount",
            "TenantPaymentMode",
            "TenantPaymentAccountStatus",
            "TenantAuthProviderStatus"
        };

        var allowedPathFragments = new[]
        {
            $"{Path.DirectorySeparatorChar}Persistence{Path.DirectorySeparatorChar}Migrations{Path.DirectorySeparatorChar}"
        };

        var violations = sourceRoots
            .SelectMany(directory => Directory.EnumerateFiles(
                Path.Combine(root, directory),
                "*.cs",
                SearchOption.AllDirectories))
            .Where(path => !allowedPathFragments.Any(fragment => path.Contains(fragment, StringComparison.Ordinal)))
            .SelectMany(path => File.ReadLines(path)
                .Select((line, index) => new { path, line, lineNumber = index + 1 }))
            .Where(candidate => forbidden.Any(symbol =>
                candidate.line.Contains(symbol, StringComparison.Ordinal)))
            .Select(candidate => $"{Path.GetRelativePath(root, candidate.path)}:{candidate.lineNumber}")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"Forbidden external-provider coupling was found:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    [Fact]
    public void Production_code_does_not_reference_removed_wechat_payment_sdk()
    {
        var root = FindRepositoryRoot();
        var sourceRoots = new[] { "Tiku.Api", "Tiku.Application", "Tiku.Domain", "Tiku.Infrastructure" };
        var forbidden = new[]
        {
            "SKIT.FlurlHttpClient.Wechat"
        };

        var violations = sourceRoots
            .SelectMany(directory => Directory.EnumerateFiles(
                Path.Combine(root, directory),
                "*.cs",
                SearchOption.AllDirectories))
            .Where(path => !path.Contains(
                $"{Path.DirectorySeparatorChar}Persistence{Path.DirectorySeparatorChar}Migrations{Path.DirectorySeparatorChar}",
                StringComparison.Ordinal))
            .SelectMany(path => File.ReadLines(path)
                .Select((line, index) => new { path, line, lineNumber = index + 1 }))
            .Where(candidate => forbidden.Any(symbol =>
                candidate.line.Contains(symbol, StringComparison.Ordinal)))
            .Select(candidate => $"{Path.GetRelativePath(root, candidate.path)}:{candidate.lineNumber}")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"Removed WeChat payment SDK references were found:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    [Fact]
    public void Business_layers_do_not_reference_third_party_provider_sdks()
    {
        var root = FindRepositoryRoot();
        var sourceRoots = new[] { "Tiku.Api", "Tiku.Application", "Tiku.Domain" };
        var forbidden = new[]
        {
            "AlibabaCloud.",
            "Aliyun.OSS",
            "Senparc.Weixin",
            "AlipaySDKNet",
            "Aop.Api",
            "Microsoft.SemanticKernel"
        };

        var violations = sourceRoots
            .SelectMany(directory => Directory.EnumerateFiles(
                Path.Combine(root, directory),
                "*.cs",
                SearchOption.AllDirectories))
            .SelectMany(path => File.ReadLines(path)
                .Select((line, index) => new { path, line, lineNumber = index + 1 }))
            .Where(candidate => forbidden.Any(symbol =>
                candidate.line.Contains(symbol, StringComparison.Ordinal)))
            .Select(candidate => $"{Path.GetRelativePath(root, candidate.path)}:{candidate.lineNumber}")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"Business-layer third-party provider SDK references were found:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }

    private static string FindRepositoryRoot()
    {
        var directory = new DirectoryInfo(AppContext.BaseDirectory);
        while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "TIKU-BACKEND.slnx")))
            directory = directory.Parent;

        return directory?.FullName ?? throw new DirectoryNotFoundException("Repository root was not found.");
    }

    private static void AssertNoForbiddenSymbols(
        string root,
        IEnumerable<string> files,
        IReadOnlyCollection<string> forbidden,
        string failureMessage)
    {
        var violations = files
            .SelectMany(path => File.ReadLines(path)
                .Select((line, index) => new { path, line, lineNumber = index + 1 }))
            .Where(candidate => forbidden.Any(symbol =>
                candidate.line.Contains(symbol, StringComparison.Ordinal)))
            .Select(candidate => $"{Path.GetRelativePath(root, candidate.path)}:{candidate.lineNumber}")
            .ToArray();

        Assert.True(
            violations.Length == 0,
            $"{failureMessage}:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}");
    }
}
