using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.DTOs.Company;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;
using PowderCoating.Core.Interfaces.Services;
using PowderCoating.Shared.Constants;
using PowderCoating.Web.Extensions;
using System.Security.Claims;

namespace PowderCoating.Web.Controllers;

/// <summary>
/// Controller for managing companies (SuperAdmin only)
/// </summary>
[Authorize(Policy = AppConstants.Policies.SuperAdminOnly)]
public class CompaniesController : Controller
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly ISeedDataService _seedDataService;
    private readonly ICompanyListService _companyList;
    private readonly ICompanyDataPurgeService _companyPurge;
    private readonly IAuditLogService _auditLog;
    private readonly IInAppNotificationService _inApp;
    private readonly ILogger<CompaniesController> _logger;

    public CompaniesController(
        IUnitOfWork unitOfWork,
        IMapper mapper,
        UserManager<ApplicationUser> userManager,
        ISeedDataService seedDataService,
        ICompanyListService companyList,
        ICompanyDataPurgeService companyPurge,
        IAuditLogService auditLog,
        IInAppNotificationService inApp,
        ILogger<CompaniesController> logger)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
        _userManager = userManager;
        _seedDataService = seedDataService;
        _companyList = companyList;
        _companyPurge = companyPurge;
        _auditLog = auditLog;
        _inApp = inApp;
        _logger = logger;
    }

    /// <summary>
    /// Lists all non-deleted tenant companies with search, sort, and pagination. Bypasses the
    /// global multi-tenancy query filter with <c>IgnoreQueryFilters()</c> because SuperAdmins
    /// need a cross-company view. Job, quote, and customer counts are fetched in three separate
    /// <c>GROUP BY</c> queries rather than loading related collections, avoiding N+1 behaviour
    /// on large tenants. The current impersonation context is surfaced to the view so the UI
    /// can highlight the company being impersonated.
    /// </summary>
    // GET: Companies
    public async Task<IActionResult> Index(
        string? searchTerm,
        string sortColumn = "CompanyName",
        string sortDirection = "asc",
        int pageNumber = 1,
        int pageSize = 25,
        bool showChurned = false)
    {
        try
        {
            pageNumber = Math.Max(1, pageNumber);
            pageSize = pageSize is 10 or 25 or 50 or 100 ? pageSize : 25;

            var (companies, totalCount, churnedCount) = await _companyList.GetPagedAsync(
                searchTerm, sortColumn, sortDirection, pageNumber, pageSize, hideChurned: !showChurned);

            var companyDtos = _mapper.Map<List<CompanyListDto>>(companies);

            if (companyDtos.Count > 0)
            {
                var ids = companyDtos.Select(c => c.Id).ToList();
                var summary = await _companyList.GetCountSummaryAsync(ids);
                var companyById = companies.ToDictionary(c => c.Id);
                var now = DateTime.UtcNow;

                foreach (var dto in companyDtos)
                {
                    dto.JobCount      = summary.JobCounts.GetValueOrDefault(dto.Id, 0);
                    dto.QuoteCount    = summary.QuoteCounts.GetValueOrDefault(dto.Id, 0);
                    dto.CustomerCount = summary.CustomerCounts.GetValueOrDefault(dto.Id, 0);

                    if (summary.WizardInfo.TryGetValue(dto.Id, out var w))
                    {
                        dto.WizardCompleted      = true;
                        dto.WizardCompletedAt    = w.CompletedAt;
                        dto.WizardCompletedByName = w.CompletedByName;
                    }

                    // Health badge
                    var lastLogin  = summary.LastLoginDates.TryGetValue(dto.Id, out var ll) ? ll : null;
                    var daysSince  = lastLogin.HasValue ? (int)(now - lastLogin.Value).TotalDays : -1;
                    var j30        = summary.Jobs30Counts.GetValueOrDefault(dto.Id, 0);
                    var j90        = summary.Jobs90Counts.GetValueOrDefault(dto.Id, 0);

                    if (companyById.TryGetValue(dto.Id, out var co))
                    {
                        var (score, _) = CompanyHealthHelper.ComputeHealth(co, daysSince, j30, j90, dto.JobCount, now);
                        var neverActivated = dto.JobCount == 0 && dto.CustomerCount == 0 && dto.QuoteCount == 0
                                            && dto.CreatedAt < now.AddDays(-7);
                        dto.HealthScore  = score;
                        dto.HealthRisk   = CompanyHealthHelper.ToRiskLevel(score, neverActivated).ToString();
                    }

                    dto.LastLoginDate = lastLogin;
                }
            }

            ViewBag.PlanConfigs = (await _unitOfWork.SubscriptionPlanConfigs.FindAsync(
                c => c.IsActive, ignoreQueryFilters: true)).OrderBy(c => c.SortOrder).ToList();

            ViewBag.SearchTerm = searchTerm;
            ViewBag.SortColumn = sortColumn;
            ViewBag.SortDirection = sortDirection;
            ViewBag.TotalCount = totalCount;
            ViewBag.PageNumber = pageNumber;
            ViewBag.PageSize = pageSize;
            ViewBag.TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
            ViewBag.ImpersonatingCompanyId = HttpContext.Session.GetInt32("ImpersonatingCompanyId");
            ViewBag.ShowChurned = showChurned;
            ViewBag.ChurnedCount = churnedCount;

            return View(companyDtos);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving companies list");
            TempData["Error"] = "An error occurred while loading companies.";
            return View(new List<CompanyListDto>());
        }
    }

    /// <summary>
    /// Begins a SuperAdmin impersonation session for the specified company. Stores the target
    /// company ID and name in the server-side session so that <see cref="ITenantContext"/> resolves
    /// to that company for the duration of the session. The action is intentionally logged at
    /// Warning level because impersonation is a privileged escalation that should appear in
    /// audit trails without requiring a full audit-log query.
    /// </summary>
    // POST: Companies/StartImpersonating
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> StartImpersonating(int companyId)
    {
        var company = await _unitOfWork.Companies.GetByIdAsync(companyId, ignoreQueryFilters: true);
        if (company == null || company.IsDeleted)
            return NotFound();

        HttpContext.Session.SetInt32("ImpersonatingCompanyId", companyId);
        HttpContext.Session.SetString("ImpersonatingCompanyName", company.CompanyName);

        _logger.LogWarning("SuperAdmin {User} started impersonating company {Id} ({Name})",
            User.Identity?.Name, companyId, company.CompanyName);

        TempData["SuccessMessage"] = $"Now impersonating {company.CompanyName}. All data is now scoped to this company.";
        return RedirectToAction("Index", "Dashboard");
    }

    /// <summary>
    /// Ends the current impersonation session by removing the company ID and name keys from the
    /// server-side session, restoring the SuperAdmin to the full platform view. The company name
    /// is captured before removal so it can appear in the success message even after the session
    /// key is gone.
    /// </summary>
    // POST: Companies/StopImpersonating
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult StopImpersonating()
    {
        var name = HttpContext.Session.GetString("ImpersonatingCompanyName") ?? "company";
        HttpContext.Session.Remove("ImpersonatingCompanyId");
        HttpContext.Session.Remove("ImpersonatingCompanyName");

        _logger.LogInformation("SuperAdmin {User} stopped impersonating {Name}",
            User.Identity?.Name, name);

        TempData["SuccessMessage"] = "Impersonation ended. You are back to full platform view.";
        return RedirectToAction(nameof(Index));
    }

    /// <summary>
    /// Shows the full details page for a single tenant company, including its user list,
    /// customers, and jobs. Uses <c>ignoreQueryFilters: true</c> so deleted companies can
    /// still be inspected by a SuperAdmin without needing to manually bypass the soft-delete
    /// filter.
    /// </summary>
    // GET: Companies/Details/5
    public async Task<IActionResult> Details(int id)
    {
        try
        {
            var company = await _unitOfWork.Companies
                .GetByIdAsync(id, ignoreQueryFilters: true,
                    c => c.Users,
                    c => c.Customers,
                    c => c.Jobs,
                    c => c.Preferences!);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            var companyDto = _mapper.Map<CompanyDto>(company);

            ViewBag.PlanConfigs = (await _unitOfWork.SubscriptionPlanConfigs.FindAsync(
                c => c.IsActive, ignoreQueryFilters: true)).OrderBy(c => c.SortOrder).ToList();

            // Health data
            var summary    = await _companyList.GetCountSummaryAsync(new[] { id });
            var now        = DateTime.UtcNow;
            var lastLogin  = summary.LastLoginDates.TryGetValue(id, out var ll) ? ll : null;
            var daysSince  = lastLogin.HasValue ? (int)(now - lastLogin.Value).TotalDays : -1;
            var j30        = summary.Jobs30Counts.GetValueOrDefault(id, 0);
            var j90        = summary.Jobs90Counts.GetValueOrDefault(id, 0);
            var totalJobs  = companyDto.JobCount;
            var totalCust  = companyDto.CustomerCount;
            var totalQuotes = summary.QuoteCounts.GetValueOrDefault(id, 0);

            var (healthScore, healthSignals) = CompanyHealthHelper.ComputeHealth(company, daysSince, j30, j90, totalJobs, now);
            var neverActivated = totalJobs == 0 && totalCust == 0 && totalQuotes == 0
                                 && company.CreatedAt < now.AddDays(-7);
            var riskLevel = CompanyHealthHelper.ToRiskLevel(healthScore, neverActivated);

            ViewBag.HealthScore   = healthScore;
            ViewBag.HealthRisk    = riskLevel.ToString();
            ViewBag.HealthSignals = healthSignals;
            ViewBag.Jobs30        = j30;
            ViewBag.Jobs90        = j90;
            ViewBag.LastLoginDate = lastLogin;

            // Onboarding data (from Preferences)
            var prefs = company.Preferences;
            int steps = 0;
            if (prefs?.FirstJobCreatedAt.HasValue == true || prefs?.FirstQuoteCreatedAt.HasValue == true) steps++;
            if (prefs?.FirstInvoiceCreatedAt.HasValue == true) steps++;
            if (prefs?.FirstWorkflowCompletedAt.HasValue == true) steps++;

            ViewBag.Onboarding = new PowderCoating.Web.ViewModels.Platform.OnboardingProgressRowViewModel
            {
                CompanyId                    = company.Id,
                CompanyName                  = company.CompanyName ?? "",
                WizardCompleted              = prefs?.SetupWizardCompleted ?? false,
                OnboardingPath               = prefs?.OnboardingPath,
                StepsCompleted               = steps,
                TotalSteps                   = 3,
                FirstJobCreatedAt            = prefs?.FirstJobCreatedAt,
                FirstQuoteCreatedAt          = prefs?.FirstQuoteCreatedAt,
                FirstInvoiceCreatedAt        = prefs?.FirstInvoiceCreatedAt,
                FirstWorkflowCompletedAt     = prefs?.FirstWorkflowCompletedAt,
                GuidedActivationDismissedAt  = prefs?.GuidedActivationDismissedAt,
            };

            return View(companyDto);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving company details for ID {CompanyId}", id);
            TempData["Error"] = "An error occurred while loading company details.";
            return RedirectToAction(nameof(Index));
        }
    }

    /// <summary>
    /// Renders the new-company form pre-filled with today's subscription start date and
    /// <c>IsActive = true</c>. Available subscription plans are loaded from
    /// <c>SubscriptionPlanConfig</c> so the dropdown stays in sync with whatever plans are
    /// currently active in the database.
    /// </summary>
    // GET: Companies/Create
    public async Task<IActionResult> Create()
    {
        var model = new CreateCompanyDto
        {
            SubscriptionStartDate = DateTime.UtcNow,
            IsActive = true
        };

        ViewBag.PlanConfigs = (await _unitOfWork.SubscriptionPlanConfigs.FindAsync(
            c => c.IsActive, ignoreQueryFilters: true)).OrderBy(c => c.SortOrder).ToList();

        return View(model);
    }

    /// <summary>
    /// Creates a new tenant company together with its first CompanyAdmin user account. The company
    /// record is saved first so its generated ID can be assigned back to <c>Company.CompanyId</c>
    /// (a self-referencing FK used internally). Company-specific lookup tables (job statuses,
    /// priorities, etc.) are seeded immediately via <see cref="ISeedDataService.SeedCompanyLookupsAsync"/>
    /// so the new tenant can start using the app without a separate seeding step. If user creation
    /// fails after the company is saved, the company record is kept and a warning is shown — a
    /// full rollback is not attempted to avoid leaving orphaned ASP.NET Identity records.
    /// </summary>
    // POST: Companies/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(CreateCompanyDto model)
    {
        if (!ModelState.IsValid)
        {
            ViewBag.PlanConfigs = (await _unitOfWork.SubscriptionPlanConfigs.FindAsync(
                c => c.IsActive, ignoreQueryFilters: true)).OrderBy(c => c.SortOrder).ToList();
            return View(model);
        }

        try
        {
            // Check if company code is unique
            if (!string.IsNullOrWhiteSpace(model.CompanyCode))
            {
                var existingCompany = await _unitOfWork.Companies
                    .FindAsync(c => c.CompanyCode == model.CompanyCode, ignoreQueryFilters: true);

                if (existingCompany.Any())
                {
                    ModelState.AddModelError("CompanyCode", "A company with this code already exists.");
                    return View(model);
                }
            }

            // Create company
            var company = _mapper.Map<Company>(model);
            company.CompanyId = 0; // Will be set after creation

            await _unitOfWork.Companies.AddAsync(company);
            await _unitOfWork.CompleteAsync();

            // Update self-reference
            company.CompanyId = company.Id;
            await _unitOfWork.CompleteAsync();

            // Seed lookup tables for the new company
            _logger.LogInformation("Seeding lookup tables for new company {CompanyName}", company.CompanyName);
            var seedResult = await _seedDataService.SeedCompanyLookupsAsync(company.Id);
            if (!seedResult.Success)
            {
                _logger.LogWarning("Failed to seed lookup tables for company {CompanyName}: {Message}",
                    company.CompanyName, seedResult.Message);
            }
            else
            {
                _logger.LogInformation("Successfully seeded {Count} lookup items for company {CompanyName}",
                    seedResult.ItemsSeeded, company.CompanyName);
            }

            // Create admin user for the company
            var adminUser = new ApplicationUser
            {
                UserName = model.AdminEmail,
                Email = model.AdminEmail,
                EmailConfirmed = true,
                FirstName = model.AdminFirstName,
                LastName = model.AdminLastName,
                CompanyId = company.Id,
                CompanyRole = AppConstants.CompanyRoles.CompanyAdmin,
                IsActive = true,
                HireDate = DateTime.UtcNow,
                Department = "Management",
                Position = "Company Administrator",
            };
            adminUser.GrantAllPermissions();

            var result = await _userManager.CreateAsync(adminUser, model.AdminPassword);

            if (result.Succeeded)
            {
                await _userManager.AddToRoleAsync(adminUser, AppConstants.Roles.Administrator);

                _logger.LogInformation("Company {CompanyName} created successfully by {User}",
                    company.CompanyName, User.Identity?.Name);

                _ = _inApp.CreateForSuperAdminsAsync(
                    "Company Created",
                    $"{company.CompanyName} was created manually by {User.Identity?.Name}.",
                    "NewCompany",
                    $"/Companies/Details/{company.Id}");

                TempData["Success"] = $"Company '{company.CompanyName}' and admin user created successfully.";
                return RedirectToAction(nameof(Details), new { id = company.Id });
            }
            else
            {
                _logger.LogError("Failed to create admin user for company {CompanyName}: {Errors}",
                    company.CompanyName, string.Join(", ", result.Errors.Select(e => e.Description)));

                TempData["Warning"] = $"Company created but admin user creation failed: {string.Join(", ", result.Errors.Select(e => e.Description))}";
                return RedirectToAction(nameof(Details), new { id = company.Id });
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error creating company");
            ModelState.AddModelError("", "An error occurred while creating the company.");
            return View(model);
        }
    }

    /// <summary>
    /// Loads the company edit form. Uses <c>ignoreQueryFilters: true</c> so SuperAdmins can
    /// edit a company that has been soft-deleted (e.g., to reactivate it).
    /// </summary>
    // GET: Companies/Edit/5
    public async Task<IActionResult> Edit(int id)
    {
        try
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            var model = _mapper.Map<UpdateCompanyDto>(company);

            ViewBag.PlanConfigs = (await _unitOfWork.SubscriptionPlanConfigs.FindAsync(
                c => c.IsActive, ignoreQueryFilters: true)).OrderBy(c => c.SortOrder).ToList();

            return View(model);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error loading company for edit: {CompanyId}", id);
            TempData["Error"] = "An error occurred while loading the company.";
            return RedirectToAction(nameof(Index));
        }
    }

    /// <summary>
    /// Saves changes to an existing company. Validates that any changed company code remains
    /// unique across all tenants (excluding the record being edited). AutoMapper merges only
    /// the DTO fields onto the tracked entity so fields not present in the form (e.g. internal
    /// flags) are not accidentally overwritten.
    /// </summary>
    // POST: Companies/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, UpdateCompanyDto model)
    {
        if (id != model.Id)
            return NotFound();

        if (!ModelState.IsValid)
            return View(model);

        try
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            // Check if company code is unique (if changed)
            if (!string.IsNullOrWhiteSpace(model.CompanyCode) && model.CompanyCode != company.CompanyCode)
            {
                var existingCompany = await _unitOfWork.Companies
                    .FindAsync(c => c.CompanyCode == model.CompanyCode && c.Id != id, ignoreQueryFilters: true);

                if (existingCompany.Any())
                {
                    ModelState.AddModelError("CompanyCode", "A company with this code already exists.");
                    return View(model);
                }
            }

            _mapper.Map(model, company);
            await _unitOfWork.CompleteAsync();

            _logger.LogInformation("Company {CompanyName} updated successfully by {User}",
                company.CompanyName, User.Identity?.Name);

            TempData["Success"] = "Company updated successfully.";
            return RedirectToAction(nameof(Details), new { id = company.Id });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error updating company {CompanyId}", id);
            ModelState.AddModelError("", "An error occurred while updating the company.");
            return View(model);
        }
    }

    /// <summary>
    /// Flips the <c>IsActive</c> flag on a company. Deactivating a company prevents its users
    /// from logging in (the app checks <c>IsActive</c> during sign-in) but keeps all data intact
    /// so the company can be reactivated later without data loss. For a more permanent action
    /// see <see cref="SoftDelete"/>.
    /// </summary>
    // POST: Companies/ToggleActive/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ToggleActive(int id)
    {
        try
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            company.IsActive = !company.IsActive;
            await _unitOfWork.CompleteAsync();

            var status = company.IsActive ? "activated" : "deactivated";
            _logger.LogInformation("Company {CompanyName} {Status} by {User}",
                company.CompanyName, status, User.Identity?.Name);

            TempData["Success"] = $"Company {status} successfully.";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error toggling company active status for {CompanyId}", id);
            TempData["Error"] = "An error occurred while updating the company status.";
        }

        return RedirectToAction(nameof(Index));
    }

    /// <summary>
    /// Soft-deletes a company by setting <c>IsDeleted = true</c> and <c>IsActive = false</c>,
    /// and deactivates all of its users so they can no longer log in. All business data is
    /// preserved for potential recovery. An audit log entry is written to <c>AuditLog</c>
    /// recording who performed the action and how many users were affected. This is less
    /// destructive than <see cref="HardDelete"/> and should be the default deactivation path.
    /// </summary>
    // POST: Companies/SoftDelete/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> SoftDelete(int id)
    {
        try
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(id,
                ignoreQueryFilters: true,
                c => c.Users);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            var adminUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
            var adminName = User.Identity?.Name ?? "Unknown";
            var companyName = company.CompanyName;
            var userCount = company.Users.Count;

            company.IsDeleted = true;
            company.IsActive = false;
            company.UpdatedAt = DateTime.UtcNow;

            foreach (var user in company.Users)
            {
                user.IsActive = false;
                await _userManager.UpdateAsync(user);
            }

            await _unitOfWork.CompleteAsync();

            await _auditLog.LogAsync(new AuditLog
            {
                UserId = adminUserId,
                UserName = adminName,
                CompanyId = id,
                CompanyName = companyName,
                Action = "SoftDelete",
                EntityType = "Company",
                EntityId = id.ToString(),
                EntityDescription = companyName,
                NewValues = $"IsDeleted=true, IsActive=false. {userCount} user(s) deactivated.",
                Timestamp = DateTime.UtcNow,
                IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString()
            });

            _logger.LogWarning("Company {CompanyName} (ID:{CompanyId}) soft-deleted by {User}",
                companyName, id, adminName);

            TempData["Success"] = $"Company '{companyName}' deactivated. {userCount} user(s) disabled.";
            return RedirectToAction(nameof(Index));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error soft-deleting company {CompanyId}", id);
            TempData["Error"] = "An error occurred while deactivating the company.";
            return RedirectToAction(nameof(Index));
        }
    }

    /// <summary>
    /// Permanently deletes a company and every piece of data associated with it across all
    /// tables. Requires the caller to type "DELETE" as a confirmation string to prevent
    /// accidental invocation. Deletion is ordered leaf-to-root across six tiers to respect
    /// foreign key constraints without disabling constraint checks. ASP.NET Identity users are
    /// deleted via <c>UserManager.DeleteAsync</c> so the Identity framework cascades to its
    /// own AspNetUser* tables correctly. An audit log entry is written before the method returns
    /// so there is a permanent record even though the company record itself is gone.
    /// This is irreversible — prefer <see cref="SoftDelete"/> for normal deactivations.
    /// </summary>
    // POST: Companies/HardDelete/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> HardDelete(int id, string confirmation)
    {
        if (confirmation != "DELETE")
        {
            TempData["Error"] = "Hard delete requires typing DELETE to confirm.";
            return RedirectToAction(nameof(Index));
        }

        var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);

        if (company == null)
        {
            TempData["Error"] = "Company not found.";
            return RedirectToAction(nameof(Index));
        }

        var adminUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var adminName = User.Identity?.Name ?? "Unknown";
        var companyName = company.CompanyName;

        try
        {
            // Load user IDs first — needed for announcement-dismissal cleanup in the purge service
            var userIds = await _userManager.Users.IgnoreQueryFilters()
                .Where(u => u.CompanyId == id).Select(u => u.Id).ToListAsync();

            // Tiers 1-4: bulk delete all business data (service mirrors the original tier ordering)
            await _companyPurge.DeleteAllBusinessDataAsync(id, userIds);

            // Tier 5: delete Identity users so AspNetUser* tables cascade correctly
            var users = await _userManager.Users.IgnoreQueryFilters()
                .Where(u => u.CompanyId == id).ToListAsync();
            var userCount = users.Count;
            foreach (var user in users)
                await _userManager.DeleteAsync(user);

            // Tier 6: delete company record
            await _unitOfWork.Companies.DeleteAsync(company);
            await _unitOfWork.CompleteAsync();

            await _auditLog.LogAsync(new AuditLog
            {
                UserId = adminUserId,
                UserName = adminName,
                CompanyId = null,
                CompanyName = companyName,
                Action = "HardDelete",
                EntityType = "Company",
                EntityId = id.ToString(),
                EntityDescription = companyName,
                OldValues = $"Company '{companyName}' (ID:{id}) permanently deleted. {userCount} user(s) removed.",
                Timestamp = DateTime.UtcNow,
                IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString()
            });

            _logger.LogWarning("Company {CompanyName} (ID:{CompanyId}) HARD DELETED by {User}. {UserCount} users removed.",
                companyName, id, adminName, userCount);

            TempData["Success"] = $"Company '{companyName}' and all associated data permanently deleted ({userCount} user(s) removed).";
            return RedirectToAction(nameof(Index));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error hard-deleting company {CompanyId}", id);
            TempData["Error"] = $"An error occurred during deletion: {ex.Message}. Some data may have been partially removed.";
            return RedirectToAction(nameof(Index));
        }
    }

    /// <summary>
    /// Permanently deletes all business data for a company (jobs, quotes, customers, invoices,
    /// inventory, etc.) while preserving the company record, its users, operating costs,
    /// preferences, and lookup tables. Useful for resetting a demo or trial tenant back to a
    /// clean state. Requires the caller to type "DELETE" as confirmation. Deletion is ordered
    /// across three tiers (grandchildren → children → top-level entities) to avoid FK violations.
    /// The QuickBooks migration wizard progress is also cleared. An audit log entry is written
    /// to record the action. This operation is irreversible.
    /// </summary>
    // POST: Companies/ResetData/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ResetData(int id, string confirmation)
    {
        if (confirmation != "DELETE")
        {
            TempData["Error"] = "Data reset requires typing DELETE to confirm.";
            return RedirectToAction(nameof(Details), new { id });
        }

        var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);

        if (company == null)
        {
            TempData["Error"] = "Company not found.";
            return RedirectToAction(nameof(Index));
        }

        var adminUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var adminName   = User.Identity?.Name ?? "Unknown";
        var companyName = company.CompanyName;

        try
        {
            await _companyPurge.ResetBusinessDataAsync(id);

            await _auditLog.LogAsync(new AuditLog
            {
                UserId           = adminUserId,
                UserName         = adminName,
                CompanyId        = id,
                CompanyName      = companyName,
                Action           = "ResetData",
                EntityType       = "Company",
                EntityId         = id.ToString(),
                EntityDescription = companyName,
                OldValues        = $"All business data for company '{companyName}' (ID:{id}) permanently deleted by {adminName}. Company record, users, and settings preserved.",
                Timestamp        = DateTime.UtcNow,
                IpAddress        = HttpContext.Connection.RemoteIpAddress?.ToString()
            });

            _logger.LogWarning(
                "Company {CompanyName} (ID:{CompanyId}) ALL BUSINESS DATA RESET by {User}.",
                companyName, id, adminName);

            TempData["SuccessPermanent"] = $"All business data for '{companyName}' has been permanently deleted. The company record, users, and configuration have been preserved.";
            return RedirectToAction(nameof(Details), new { id });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error resetting data for company {CompanyId}", id);
            TempData["ErrorPermanent"] = $"An error occurred during data reset: {ex.Message}. Some data may have been partially removed.";
            return RedirectToAction(nameof(Details), new { id });
        }
    }

    /// <summary>
    /// One-time data repair for a company whose chart of accounts was imported from QuickBooks
    /// IIF files. QuickBooks stores credit-normal account opening balances as negative numbers
    /// (e.g. Revenue, Liability, Equity), but this app's convention is positive opening balances
    /// with the debit/credit nature implied by account type. This flips negative opening balances
    /// on Revenue/Liability/Equity accounts to their absolute values so the chart of accounts
    /// reads correctly. Afterward, Recalculate Balances (on the Chart of Accounts page) should be
    /// run to propagate the corrected opening balances into CurrentBalance.
    /// <para>
    /// This is a SuperAdmin-only platform tool — it operates on the target company identified by
    /// <paramref name="id"/> (not the caller's tenant), so it uses <c>ignoreQueryFilters</c> to
    /// reach across the multi-tenancy boundary. It was deliberately moved here from the company
    /// Chart of Accounts page so normal company admins can't see or trigger it.
    /// </para>
    /// </summary>
    // POST: Companies/FixOpeningBalanceSigns/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> FixOpeningBalanceSigns(int id)
    {
        var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);
        if (company == null)
        {
            TempData["Error"] = "Company not found.";
            return RedirectToAction(nameof(Index));
        }

        try
        {
            // Explicit CompanyId predicate + ignoreQueryFilters: SuperAdmin acts on another
            // tenant, so the global multi-tenancy filter must be bypassed but scoping kept tight.
            var accounts = await _unitOfWork.Accounts.FindAsync(
                a => a.CompanyId == id && a.IsActive, ignoreQueryFilters: true);

            int fixedCount = 0;
            foreach (var acct in accounts)
            {
                if (acct.OpeningBalance < 0 &&
                    acct.AccountType is Core.Enums.AccountType.Revenue
                                     or Core.Enums.AccountType.Liability
                                     or Core.Enums.AccountType.Equity)
                {
                    acct.OpeningBalance = Math.Abs(acct.OpeningBalance);
                    acct.CurrentBalance = Math.Abs(acct.CurrentBalance);
                    await _unitOfWork.Accounts.UpdateAsync(acct);
                    fixedCount++;
                }
            }
            await _unitOfWork.CompleteAsync();

            TempData[fixedCount > 0 ? "Success" : "Info"] = fixedCount > 0
                ? $"Fixed {fixedCount} account(s) with negative opening balances for '{company.CompanyName}'. Run Recalculate Balances on the company's Chart of Accounts to update CurrentBalance."
                : $"No accounts needed fixing for '{company.CompanyName}' — all opening balances already have the correct sign.";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fixing opening balance signs for company {CompanyId}", id);
            TempData["Error"] = "An error occurred while fixing opening balances.";
        }

        return RedirectToAction(nameof(Details), new { id });
    }

    /// <summary>
    /// Renders the form for adding an additional CompanyAdmin user to an existing company.
    /// Used when a company needs more than one admin or when the original admin's account must
    /// be replaced (e.g., employee departure). This is a SuperAdmin action; company admins
    /// create regular users via <c>CompanyUsersController</c>.
    /// </summary>
    // GET: Companies/CreateCompanyAdmin/5
    public async Task<IActionResult> CreateCompanyAdmin(int id)
    {
        try
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(id, ignoreQueryFilters: true);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            var model = new CreateCompanyAdminDto
            {
                CompanyId = company.Id,
                CompanyName = company.CompanyName
            };

            return View(model);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error loading create company admin page for company {CompanyId}", id);
            TempData["Error"] = "An error occurred while loading the page.";
            return RedirectToAction(nameof(Index));
        }
    }

    /// <summary>
    /// Creates a new CompanyAdmin Identity user for the specified company. Grants all
    /// per-company permissions by default (same set as the initial admin created in
    /// <see cref="Create(CreateCompanyDto)"/>) and assigns the ASP.NET Identity
    /// <c>Administrator</c> role. Email uniqueness is checked before creation; if the email
    /// already exists the form is returned with a model error rather than throwing an exception.
    /// </summary>
    // POST: Companies/CreateCompanyAdmin
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> CreateCompanyAdmin(CreateCompanyAdminDto model)
    {
        if (!ModelState.IsValid)
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(model.CompanyId, ignoreQueryFilters: true);
            if (company != null)
                model.CompanyName = company.CompanyName;
            return View(model);
        }

        try
        {
            var company = await _unitOfWork.Companies.GetByIdAsync(model.CompanyId, ignoreQueryFilters: true);

            if (company == null)
            {
                TempData["Error"] = "Company not found.";
                return RedirectToAction(nameof(Index));
            }

            var existingUser = await _userManager.FindByEmailAsync(model.Email);
            if (existingUser != null)
            {
                ModelState.AddModelError("Email", "A user with this email already exists.");
                model.CompanyName = company.CompanyName;
                return View(model);
            }

            var adminUser = new ApplicationUser
            {
                UserName = model.Email,
                Email = model.Email,
                EmailConfirmed = true,
                FirstName = model.FirstName,
                LastName = model.LastName,
                CompanyId = company.Id,
                CompanyRole = AppConstants.CompanyRoles.CompanyAdmin,
                IsActive = true,
                HireDate = DateTime.UtcNow,
                Department = model.Department ?? "Management",
                Position = model.Position ?? "Company Administrator",
                PhoneNumber = model.Phone,
            };
            adminUser.GrantAllPermissions();

            var result = await _userManager.CreateAsync(adminUser, model.Password);

            if (result.Succeeded)
            {
                await _userManager.AddToRoleAsync(adminUser, AppConstants.Roles.Administrator);

                _logger.LogInformation("Company admin {Email} created for company {CompanyName} by SuperAdmin {User}",
                    adminUser.Email, company.CompanyName, User.Identity?.Name);

                TempData["Success"] = $"Company admin user '{adminUser.FullName}' created successfully for {company.CompanyName}.";
                return RedirectToAction(nameof(Details), new { id = company.Id });
            }
            else
            {
                foreach (var error in result.Errors)
                    ModelState.AddModelError("", error.Description);
                model.CompanyName = company.CompanyName;
                return View(model);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error creating company admin for company {CompanyId}", model.CompanyId);
            ModelState.AddModelError("", "An error occurred while creating the admin user.");
            return View(model);
        }
    }

    // ─── User Login History ──────────────────────────────────────────────────

    /// <summary>
    /// Returns profile info and the last 50 login-related audit entries for a single user,
    /// used by the user-details offcanvas on the Company Details page. Verifies that the
    /// requested user actually belongs to the specified company so that a SuperAdmin cannot
    /// use this endpoint to pull audit data for users in other companies by guessing IDs.
    /// </summary>
    [HttpGet]
    public async Task<IActionResult> UserLoginHistory(int companyId, string userId)
    {
        try
        {
            var user = await _userManager.Users
                .IgnoreQueryFilters()
                .FirstOrDefaultAsync(u => u.Id == userId && u.CompanyId == companyId);

            if (user == null)
                return NotFound(new { error = "User not found." });

            // Use the viewed company's timezone so timestamps match the tenant's local time
            var company = await _unitOfWork.Companies.GetByIdAsync(companyId, ignoreQueryFilters: true);
            var tz = company?.TimeZone;

            var logs = new List<dynamic>();
            try
            {
                var rawLogs = await _auditLog.GetUserActivityAsync(userId);

                logs = rawLogs.Select(l => (dynamic)new
                {
                    action    = l.Action,
                    ipAddress = l.IpAddress ?? "—",
                    timestamp = l.Timestamp.Tz(tz).ToString("MMM d, yyyy h:mm tt"),
                    note      = TryParseNote(l.NewValues)
                }).ToList();
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Could not load audit log history for user {UserId}", userId);
                // Login history is non-critical — return user data with empty history
            }

            return Json(new
            {
                user = new
                {
                    id             = user.Id,
                    fullName       = user.FullName,
                    email          = user.Email,
                    companyRole    = user.CompanyRole,
                    department     = user.Department,
                    position       = user.Position,
                    phone          = user.PhoneNumber,
                    isActive       = user.IsActive,
                    emailConfirmed = user.EmailConfirmed,
                    hireDate       = user.HireDate != default ? user.HireDate.ToString("MMM d, yyyy") : null,
                    lastLoginDate  = user.LastLoginDate.HasValue
                        ? user.LastLoginDate.Value.Tz(tz).ToString("MMM d, yyyy h:mm tt")
                        : null,
                    createdAt      = user.CreatedAt.Tz(tz).ToString("MMM d, yyyy")
                },
                loginHistory = logs
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error loading user login history for user {UserId} in company {CompanyId}", userId, companyId);
            return StatusCode(500, new { error = ex.Message });
        }
    }

    /// <summary>
    /// Safely extracts the "note" string from the JSON stored in <c>AuditLog.NewValues</c>
    /// (e.g., <c>{"note":"Company 'Acme' is deactivated"}</c>). Returns null if the field is
    /// absent or the JSON cannot be parsed, so the UI can omit the column cleanly.
    /// </summary>
    private static string? TryParseNote(string? json)
    {
        if (string.IsNullOrWhiteSpace(json)) return null;
        try
        {
            using var doc = System.Text.Json.JsonDocument.Parse(json);
            return doc.RootElement.TryGetProperty("note", out var prop) ? prop.GetString() : null;
        }
        catch { return null; }
    }
}
