using System.Net;
using System.Net.Http.Json;
using CashLineERP.Application.Identity;
using CashLineERP.Contracts.Auth;
using CashLineERP.Contracts.Identity;
using CashLineERP.Infrastructure.Persistence.Repositories;

namespace CashLineERP.Api.Tests;

public sealed class AdminUserManagementQaTests : IClassFixture<ApiTestFactory>
{
    private readonly HttpClient _client;

    public AdminUserManagementQaTests(ApiTestFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public void Administrator_Can_Create_And_Control_User_Lifecycle_End_To_End()
    {
        using var dbContext = QaDbContextFactory.CreateContext();
        var adminService = new AdminUserService(new AdminUserRepository(dbContext));
        var identityService = new IdentityService(new IdentityReadRepository(dbContext), new FakeAccessTokenService());

        var administrator = identityService.GetCurrentUserProfile(6);
        Assert.Equal("administrator", administrator.Workspace);
        Assert.Contains("SUPER_ADMIN", administrator.RoleCodes);

        var created = adminService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "managed.ops@uat.cashline.test",
            DisplayName: "Managed Operations User",
            UserTypeCode: "internal",
            Password: "Managed@12345",
            Email: "managed.ops@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ADMIN" }),
            actorUserId: 6);

        Assert.True(created.IsActive);
        Assert.False(created.IsLocked);
        Assert.Equal("ACTIVE", created.LifecycleStatusCode);
        Assert.Contains("ADMIN", created.RoleCodes);
        Assert.Contains("OPERATIONS_WORKBENCH", created.FunctionCodes);

        var createdLogin = identityService.Login(new LoginRequest("managed.ops@uat.cashline.test", "Managed@12345", "internal"));
        Assert.False(string.IsNullOrWhiteSpace(createdLogin.AccessToken));

        var held = adminService.HoldUser(created.UserId, new AdminUserLifecycleActionRequest("Temporary investigation."), 6);
        Assert.True(held.IsLocked);
        Assert.Equal("ON_HOLD", held.LifecycleStatusCode);
        Assert.Equal("Temporary investigation.", held.LastAdminActionReason);
        Assert.Throws<InvalidOperationException>(() => identityService.Login(new LoginRequest("managed.ops@uat.cashline.test", "Managed@12345", "internal")));

        var reinstated = adminService.ReinstateUser(created.UserId, new AdminUserLifecycleActionRequest("Investigation cleared."), 6);
        Assert.True(reinstated.IsActive);
        Assert.False(reinstated.IsLocked);
        Assert.Equal("ACTIVE", reinstated.LifecycleStatusCode);

        var updatedRoles = adminService.ReplaceRoles(created.UserId, new UpdateAdminManagedUserRolesRequest(new[] { "FINANCE_MAKER" }, new[] { "PAYMENT_INITIATION" }), 6);
        Assert.DoesNotContain("ADMIN", updatedRoles.RoleCodes);
        Assert.Contains("FINANCE_MAKER", updatedRoles.RoleCodes);
        Assert.Equal(new[] { "PAYMENT_INITIATION" }, updatedRoles.FunctionCodes);

        var suspended = adminService.SuspendUser(created.UserId, new AdminUserLifecycleActionRequest("User no longer requires active access."), 6);
        Assert.False(suspended.IsActive);
        Assert.True(suspended.IsLocked);
        Assert.Equal("SUSPENDED", suspended.LifecycleStatusCode);
        Assert.NotNull(suspended.DateOfSuspensionUtc);
        Assert.Throws<InvalidOperationException>(() => identityService.Login(new LoginRequest("managed.ops@uat.cashline.test", "Managed@12345", "internal")));

        adminService.ReinstateUser(created.UserId, new AdminUserLifecycleActionRequest("Access required again."), 6);
        var revoked = adminService.RevokeUser(created.UserId, new AdminUserLifecycleActionRequest("Remove all granted access."), 6);
        Assert.True(revoked.IsLocked);
        Assert.Empty(revoked.RoleCodes);

        adminService.ReinstateUser(created.UserId, new AdminUserLifecycleActionRequest("Prepare account for deletion."), 6);
        var deleted = adminService.DeleteUser(created.UserId, new AdminUserLifecycleActionRequest("User removed by administrator."), 6);
        Assert.False(deleted.IsActive);
        Assert.True(deleted.IsLocked);
        Assert.Equal("DELETED", deleted.LifecycleStatusCode);
        Assert.Empty(deleted.RoleCodes);

        var auditActions = dbContext.AuditEvents
            .Where(entry => entry.ModuleCode == "IAM" && entry.EntityName == "SEC_USER" && entry.EntityId == created.UserId)
            .Select(entry => entry.ActionCode)
            .ToArray();
        Assert.Contains("CREATED", auditActions);
        Assert.Contains("ON_HOLD", auditActions);
        Assert.Contains("REINSTATED", auditActions);
        Assert.Contains("ROLES_REPLACED", auditActions);
        Assert.Contains("SUSPENDED", auditActions);
        Assert.Contains("ACCESS_REVOKED", auditActions);
        Assert.Contains("DELETED", auditActions);

        var reportRows = adminService.GetUserAdministrationReportRows("INTERNAL");
        Assert.Contains(reportRows, row =>
            row.UserName == "Managed Operations User"
            && row.DateOfSuspensionUtc.HasValue
            && row.LatestActionReason == "User removed by administrator.");
        Assert.Contains("Managed Operations User", adminService.ExportUserAdministrationReportExcel(6, "INTERNAL"));
        Assert.NotEmpty(adminService.ExportUserAdministrationReportPdf(6, "INTERNAL"));
        Assert.Contains(dbContext.AuditEvents, entry =>
            entry.ModuleCode == "IAM"
            && entry.EntityName == "SEC_USER"
            && entry.EntityId == 0
            && entry.ActorUserId == 6
            && entry.ActionCode == "USER_ADMIN_REPORT_EXPORTED"
            && entry.NewValue != null
            && entry.NewValue.Contains("format=PDF", StringComparison.OrdinalIgnoreCase));
    }

    [Fact]
    public void Administrator_Function_Configuration_Is_Dependent_On_User_Type_And_Rejects_Invalid_Roles()
    {
        using var dbContext = QaDbContextFactory.CreateContext();
        var adminService = new AdminUserService(new AdminUserRepository(dbContext));

        var configuration = adminService.GetFunctionConfiguration();
        Assert.Contains(configuration.ExternalRoles, role => role.RoleCode == "SUPPLIER_ADMIN" && role.Functions.Contains("Supplier onboarding"));
        Assert.Contains(configuration.ExternalRoles, role => role.RoleCode == "SUPPLIER_ADMIN" && role.FunctionCodes.Contains("SUPPLIER_ONBOARDING"));
        Assert.Contains(configuration.ExternalRoles, role => role.RoleCode == "CUSTOMER_ADMIN" && role.Functions.Contains("Customer onboarding"));
        Assert.DoesNotContain(configuration.ExternalRoles, role => role.UserTypeCode == "INTERNAL");

        var invalidAssignment = Assert.Throws<InvalidOperationException>(() => adminService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "invalid.external@uat.cashline.test",
            DisplayName: "Invalid External Role User",
            UserTypeCode: "EXTERNAL",
            Password: "Invalid@12345",
            Email: "invalid.external@uat.cashline.test",
            BranchId: null,
            PartyId: 1,
            RoleCodes: new[] { "FINANCE_MAKER" }),
            actorUserId: 6));
        Assert.Contains("not valid for user type EXTERNAL", invalidAssignment.Message);

        var externalUser = adminService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "managed.supplier@uat.cashline.test",
            DisplayName: "Managed Supplier User",
            UserTypeCode: "EXTERNAL",
            Password: "Supplier@12345",
            Email: "managed.supplier@uat.cashline.test",
            BranchId: null,
            PartyId: 1,
            RoleCodes: new[] { "SUPPLIER_ADMIN" }),
            actorUserId: 6);
        Assert.Contains("SUPPLIER_ADMIN", externalUser.RoleCodes);
        Assert.Contains("SUPPLIER_ONBOARDING", externalUser.FunctionCodes);

        var invalidFunction = Assert.Throws<InvalidOperationException>(() => adminService.ReplaceRoles(
            externalUser.UserId,
            new UpdateAdminManagedUserRolesRequest(new[] { "SUPPLIER_ADMIN" }, new[] { "CUSTOMER_PAYMENTS" }),
            6));
        Assert.Contains("not valid for the selected role set", invalidFunction.Message);

        Assert.Throws<InvalidOperationException>(() => adminService.HoldUser(externalUser.UserId, new AdminUserLifecycleActionRequest(" "), 6));
    }

    [Fact]
    public async Task User_Administration_Api_Is_Restricted_To_Super_Admin()
    {
        await _client.AuthenticateAsInternalAsync();

        var forbiddenResponse = await _client.GetAsync("/api/v1/iam/users");
        Assert.Equal(HttpStatusCode.Forbidden, forbiddenResponse.StatusCode);

        _client.DefaultRequestHeaders.Authorization = null;
        await _client.AuthenticateAsAdministratorAsync();

        var usersResponse = await _client.GetAsync("/api/v1/iam/users");
        Assert.True(usersResponse.IsSuccessStatusCode, await usersResponse.Content.ReadAsStringAsync());

        var users = await usersResponse.Content.ReadFromJsonAsync<AdminUserResponse[]>();
        Assert.NotNull(users);
        Assert.Contains(users!, user =>
            user.LoginName == "administrator@uat.cashline.test"
            && user.RoleCodes.Contains("SUPER_ADMIN")
            && user.LifecycleStatusCode == "ACTIVE");

        var functionConfigurationResponse = await _client.GetAsync("/api/v1/iam/function-configuration");
        Assert.True(functionConfigurationResponse.IsSuccessStatusCode, await functionConfigurationResponse.Content.ReadAsStringAsync());

        var reportExportResponse = await _client.GetAsync("/api/v1/iam/reports/users/export?format=EXCEL");
        Assert.True(reportExportResponse.IsSuccessStatusCode, await reportExportResponse.Content.ReadAsStringAsync());
        Assert.Equal("application/vnd.ms-excel", reportExportResponse.Content.Headers.ContentType?.MediaType);

        var blockedFinanceResponse = await _client.GetAsync("/api/v1/reports/core-finance");
        Assert.Equal(HttpStatusCode.Forbidden, blockedFinanceResponse.StatusCode);
    }

    [Fact]
    public async Task Public_Privacy_Governance_Exposes_Customer_And_Supplier_Controls()
    {
        var response = await _client.GetAsync("/api/v1/public/privacy/governance?subjectTypeCode=CUSTOMER");

        Assert.True(response.IsSuccessStatusCode, await response.Content.ReadAsStringAsync());

        var payload = await response.Content.ReadFromJsonAsync<CashLineERP.Contracts.Privacy.PrivacyGovernanceResponse>();
        Assert.NotNull(payload);
        Assert.Contains(payload!.Notices, notice => notice.SubjectTypeCode == "CUSTOMER" && notice.RequiresExplicitAcceptance);
        Assert.DoesNotContain(payload.Notices, notice => notice.SubjectTypeCode == "SUPPLIER");
        Assert.Contains(payload.ConsentPurposes, purpose => purpose.PurposeCode == "LENDER_HANDOFF" && purpose.AllowsThirdPartySharing);
        Assert.Contains(payload.MaskingRules, rule => rule.FieldCode == "BANK_ACCOUNT");
        Assert.Contains(payload.ReadinessControls, control => control.ControlCode == "PRIV-EXPORT-AUDIT");
    }
}
