using foodmarket.Domain.Catalog;
using foodmarket.Domain.Organizations;
using foodmarket.Infrastructure.Identity;
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace foodmarket.Api.Seed;

public class DevDataSeeder : IHostedService
{
    private readonly IServiceProvider _services;
    private readonly IHostEnvironment _env;

    public DevDataSeeder(IServiceProvider services, IHostEnvironment env)
    {
        _services = services;
        _env = env;
    }

    public async Task StartAsync(CancellationToken ct)
    {
        // Idempotent — runs in all envs to bootstrap a usable admin + demo org.
        // Once first real user/org is set up via UI, rename/disable demo.
        // (Wired regardless of env so stage/prod first-deploy lands a working
        //  admin, otherwise nobody can log in.)

        using var scope = _services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var userMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
        var roleMgr = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();

        foreach (var role in new[] { SystemRoles.SuperAdmin, SystemRoles.Admin, SystemRoles.Cashier, SystemRoles.Storekeeper })
        {
            if (!await roleMgr.RoleExistsAsync(role))
            {
                await roleMgr.CreateAsync(new Role { Name = role });
            }
        }

        // Глобальные единицы измерения (Phase5c). Идемпотентно: если миграция
        // уже их создала, ничего не происходит. Нужно для случая, когда
        // развернули с нуля без миграции данных.
        await SeedGlobalUnitsAsync(db, ct);

        var kzt = await db.Currencies.FirstOrDefaultAsync(c => c.Code == "KZT", ct);
        var demoOrg = await db.Organizations.FirstOrDefaultAsync(o => o.Name == "Demo Market", ct);
        if (demoOrg is null)
        {
            demoOrg = new Organization
            {
                Name = "Demo Market",
                CountryCode = "KZ",
                Bin = "000000000000",
                Address = "Алматы, ул. Пример 1",
                Phone = "+7 (777) 000-00-00",
                Email = "demo@food-market.local",
                DefaultCurrencyId = kzt?.Id,
            };
            db.Organizations.Add(demoOrg);
            await db.SaveChangesAsync(ct);
        }
        else if (demoOrg.DefaultCurrencyId is null && kzt is not null)
        {
            demoOrg.DefaultCurrencyId = kzt.Id;
            await db.SaveChangesAsync(ct);
        }

        await SeedTenantReferencesAsync(db, demoOrg.Id, ct);

        // SuperAdmin платформы — без OrganizationId, видит все организации
        const string superAdminEmail = "admin@food-market.local";
        var superAdmin = await userMgr.FindByEmailAsync(superAdminEmail);
        if (superAdmin is null)
        {
            superAdmin = new User
            {
                UserName = superAdminEmail,
                Email = superAdminEmail,
                EmailConfirmed = true,
                FullName = "Platform SuperAdmin",
                OrganizationId = null,
            };
            var result = await userMgr.CreateAsync(superAdmin, "Admin12345!");
            if (result.Succeeded)
                await userMgr.AddToRoleAsync(superAdmin, SystemRoles.SuperAdmin);
        }
        else
        {
            // Исправляем старую БД: убираем OrganizationId и пересаживаем на SuperAdmin
            if (superAdmin.OrganizationId is not null)
            {
                superAdmin.OrganizationId = null;
                await userMgr.UpdateAsync(superAdmin);
            }
            var roles = await userMgr.GetRolesAsync(superAdmin);
            if (!roles.Contains(SystemRoles.SuperAdmin))
            {
                await userMgr.RemoveFromRolesAsync(superAdmin, roles);
                await userMgr.AddToRoleAsync(superAdmin, SystemRoles.SuperAdmin);
            }
        }

        // Admin демо-организации — владелец/директор Demo Market
        const string demoAdminEmail = "admin@demo-market.local";
        var demoAdmin = await userMgr.FindByEmailAsync(demoAdminEmail);
        if (demoAdmin is null)
        {
            demoAdmin = new User
            {
                UserName = demoAdminEmail,
                Email = demoAdminEmail,
                EmailConfirmed = true,
                FullName = "Demo Market Admin",
                OrganizationId = demoOrg.Id,
            };
            var result = await userMgr.CreateAsync(demoAdmin, "Admin12345!");
            if (result.Succeeded)
                await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
        }
        else
        {
            if (!await userMgr.IsInRoleAsync(demoAdmin, SystemRoles.Admin))
                await userMgr.AddToRoleAsync(demoAdmin, SystemRoles.Admin);
        }

        await SeedAdminEmployeeAsync(db, demoOrg.Id, superAdmin?.Id, ct);

        // Глобальные SystemSettings — single-row. Сидируем дефолт 30 дней
        // retention если ещё нет записи.
        var anySettings = await db.SystemSettings.AnyAsync(ct);
        if (!anySettings)
        {
            db.SystemSettings.Add(new SystemSettings { ArchiveRetentionDays = 30 });
            await db.SaveChangesAsync(ct);
        }
    }

    /// <summary>Привязывает существующего admin@food-market.local к
    /// Employee-записи с системной ролью «Администратор» — чтобы UI «Сотрудники»
    /// сразу показывал учётку с правильной ролью, а не пустой список.</summary>
    private static async Task SeedAdminEmployeeAsync(AppDbContext db, Guid orgId, Guid? adminUserId, CancellationToken ct)
    {
        if (adminUserId is null) return;
        var existing = await db.Employees.IgnoreQueryFilters()
            .FirstOrDefaultAsync(e => e.OrganizationId == orgId && e.UserId == adminUserId, ct);
        if (existing is not null) return;
        var adminRole = await db.EmployeeRoles.IgnoreQueryFilters()
            .FirstOrDefaultAsync(r => r.OrganizationId == orgId && r.IsSystem && r.Name == "Администратор", ct);
        if (adminRole is null) return;
        db.Employees.Add(new Employee
        {
            OrganizationId = orgId,
            UserId = adminUserId,
            LastName = "Admin",
            FirstName = "System",
            Position = "Администратор",
            Email = "admin@food-market.local",
            RoleId = adminRole.Id,
            IsActive = true,
        });
        await db.SaveChangesAsync(ct);
    }

    /// <summary>Bootstrap минимально-достаточного набора tenant-сущностей для
    /// новой организации: единицы измерения (ОКЕИ), типы цен (Розничная+Оптовая),
    /// «Основной склад» MAIN, «Касса 1» POS-1, и системные роли через
    /// SeedEmployeeRolesAsync. Идемпотентно: каждый блок проверяет существующие
    /// записи. Используется и при первом старте Demo, и при создании org через
    /// SuperAdmin UI.</summary>
    public static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, CancellationToken ct)
    {
        // Phase5c: единицы измерения теперь global. Подключаем к новой
        // организации все active globals через junction org_units_of_measure.
        await EnableAllActiveUnitsForOrgAsync(db, orgId, ct);

        // Сидируем PriceType ТОЛЬКО если у организации не было ни одной записи.
        // Если есть — никогда не создаём «системную копию», корректность IsSystem
        // обеспечивает миграция Phase3b_FixPriceTypeIsSystem (выбор по факту:
        // запись с максимумом ProductPrice).
        var anyPriceType = await db.PriceTypes.IgnoreQueryFilters().AnyAsync(p => p.OrganizationId == orgId, ct);
        if (!anyPriceType)
        {
            db.PriceTypes.AddRange(
                new PriceType { OrganizationId = orgId, Name = "Розничная цена", IsSystem = true, IsRequired = true, IsRetail = true, SortOrder = 0 },
                new PriceType { OrganizationId = orgId, Name = "Оптовая", SortOrder = 1 }
            );
        }

        var mainStore = await db.Stores.IgnoreQueryFilters().FirstOrDefaultAsync(s => s.OrganizationId == orgId && s.IsMain, ct);
        if (mainStore is null)
        {
            mainStore = new Store
            {
                OrganizationId = orgId,
                Name = "Основной склад",
                Code = "MAIN",
                IsMain = true,
                Address = "Алматы, ул. Пример 1",
            };
            db.Stores.Add(mainStore);
            await db.SaveChangesAsync(ct);
        }

        var anyRetail = await db.RetailPoints.IgnoreQueryFilters().AnyAsync(r => r.OrganizationId == orgId, ct);
        if (!anyRetail)
        {
            db.RetailPoints.Add(new RetailPoint
            {
                OrganizationId = orgId,
                Name = "Касса 1",
                Code = "POS-1",
                StoreId = mainStore.Id,
            });
        }

        await db.SaveChangesAsync(ct);

        await SeedEmployeeRolesAsync(db, orgId, ct);
    }

    /// <summary>Системные роли (IsSystem=true): только Администратор / Кладовщик /
    /// Кассир. Менеджеры/Закупщики/Бухгалтеры и пр. — это кастомные роли,
    /// которые создаёт администратор орги сам через UI «Настроить права ролей».
    /// Системные нельзя редактировать или удалить из UI; только просмотр.
    /// Сидируется один раз per организацию.</summary>
    private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, CancellationToken ct)
    {
        var anyRole = await db.EmployeeRoles.IgnoreQueryFilters().AnyAsync(r => r.OrganizationId == orgId, ct);
        if (anyRole) return;

        var admin = new EmployeeRole
        {
            OrganizationId = orgId,
            Name = "Администратор",
            Description = "Полный доступ ко всем разделам организации",
            IsSystem = true, SortOrder = 0,
            Permissions = RolePermissions.All(),
        };
        var keeper = new EmployeeRole
        {
            OrganizationId = orgId,
            Name = "Кладовщик",
            Description = "Приёмки, инвентаризация, остатки",
            IsSystem = true, SortOrder = 10,
            Permissions = new RolePermissions
            {
                ProductsView = true,
                SuppliesView = true, SuppliesEdit = true, SuppliesPost = true,
                StocksView = true,
            },
        };
        var cashier = new EmployeeRole
        {
            OrganizationId = orgId,
            Name = "Кассир",
            Description = "Только работа на кассе. Без доступа к веб-админке.",
            IsSystem = true, SortOrder = 20,
            Permissions = new RolePermissions
            {
                ProductsView = true,
                StocksView = true,
                RetailSalesOperate = true,
                // RetailSalesRefund по умолчанию false — админ включит при необходимости
            },
        };

        db.EmployeeRoles.AddRange(admin, keeper, cashier);
        await db.SaveChangesAsync(ct);
    }

    /// <summary>Канонический набор 5 глобальных единиц измерения (Phase5c).
    /// Идемпотентно: вставляет только отсутствующие по Code среди globals.
    /// На свежем prod-environment где миграция данных не нашла исходных
    /// tenant-rows — этот сидер обеспечит наличие минимума.</summary>
    private static async Task SeedGlobalUnitsAsync(AppDbContext db, CancellationToken ct)
    {
        var canonical = new (string Code, string Name)[]
        {
            ("796", "штука"),
            ("166", "килограмм"),
            ("112", "литр"),
            ("006", "метр"),
            ("625", "упаковка"),
        };

        var existingCodes = await db.UnitsOfMeasure
            .IgnoreQueryFilters()
            .Where(u => u.OrganizationId == null)
            .Select(u => u.Code)
            .ToListAsync(ct);

        foreach (var (code, name) in canonical)
        {
            if (!existingCodes.Contains(code))
            {
                db.UnitsOfMeasure.Add(new UnitOfMeasure { OrganizationId = null, Code = code, Name = name, IsActive = true });
            }
        }
        await db.SaveChangesAsync(ct);
    }

    /// <summary>Включить все active globals для новой организации через
    /// junction org_units_of_measure. Идемпотентно: на повторный вызов не
    /// добавляет дубликатов.</summary>
    private static async Task EnableAllActiveUnitsForOrgAsync(AppDbContext db, Guid orgId, CancellationToken ct)
    {
        var globals = await db.UnitsOfMeasure
            .IgnoreQueryFilters()
            .Where(u => u.OrganizationId == null && u.IsActive)
            .Select(u => u.Id)
            .ToListAsync(ct);

        var alreadyEnabled = await db.OrgUnitsOfMeasure
            .IgnoreQueryFilters()
            .Where(j => j.OrganizationId == orgId)
            .Select(j => j.UnitOfMeasureId)
            .ToListAsync(ct);

        foreach (var unitId in globals.Except(alreadyEnabled))
        {
            db.OrgUnitsOfMeasure.Add(new OrgUnitOfMeasure { OrganizationId = orgId, UnitOfMeasureId = unitId });
        }
        await db.SaveChangesAsync(ct);
    }

    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
