using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using JobsMedical.Web.Services;
using JobsMedical.Web.Services.Scraping;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;

namespace JobsMedical.Web.Pages.Admin;

[Authorize(Roles = "Admin")]
public class ReviewModel : PageModel
{
    private readonly AppDbContext _db;
    private readonly IListingParser _parser;
    private readonly NotificationService _notify;

    public ReviewModel(AppDbContext db, IListingParser parser, NotificationService notify)
    {
        _db = db;
        _parser = parser;
        _notify = notify;
    }

    public RawListing? Raw { get; private set; }
    public ParsedListing? Parsed { get; private set; }
    public List<Facility> Facilities { get; private set; } = new();
    public List<Role> Roles { get; private set; } = new();
    public List<City> Cities { get; private set; } = new();

    [TempData] public string? Error { get; set; }

    // The editable form (prefilled from the parser, admin can override everything).
    [BindProperty] public ListingKind Kind { get; set; }
    [BindProperty] public int FacilityId { get; set; }
    [BindProperty] public string? NewFacilityName { get; set; }   // create a facility on the fly if none picked
    /// <summary>One or more roles — an ad like «پرستار سالمند و کودک» publishes one listing per role.</summary>
    [BindProperty] public int[] RoleIds { get; set; } = Array.Empty<int>();
    [BindProperty] public string? Description { get; set; }
    // Shift fields
    [BindProperty] public DateOnly ShiftDate { get; set; }
    [BindProperty] public ShiftType ShiftType { get; set; }
    [BindProperty] public TimeOnly StartTime { get; set; }
    [BindProperty] public TimeOnly EndTime { get; set; }
    [BindProperty] public long? PayAmount { get; set; }
    [BindProperty] public int? SharePercent { get; set; }
    [BindProperty] public bool Negotiable { get; set; }
    [BindProperty] public Gender GenderRequirement { get; set; }
    // Job fields
    [BindProperty] public string? Title { get; set; }
    [BindProperty] public EmploymentType EmploymentType { get; set; }
    [BindProperty] public long? SalaryMin { get; set; }
    [BindProperty] public long? SalaryMax { get; set; }
    // Talent («آماده به کار») fields — no facility; contact phone is key.
    [BindProperty] public int TalentCityId { get; set; }
    [BindProperty] public string? PersonName { get; set; }
    [BindProperty] public int? YearsExperience { get; set; }
    [BindProperty] public bool IsLicensed { get; set; }
    [BindProperty] public string? AreaNote { get; set; }
    [BindProperty] public string? Phone { get; set; }

    public async Task<IActionResult> OnGetAsync(int id)
    {
        await LoadListsAsync();
        Raw = await _db.RawListings.FirstOrDefaultAsync(r => r.Id == id);
        if (Raw is null) return NotFound();

        Parsed = _parser.Parse(Raw.RawText,
            Roles.Select(r => r.Name), await CityNamesAsync(), await DistrictNamesAsync());

        // Prefill the form from the parser's best guess.
        Kind = Parsed.Kind;
        var matchedRole = Roles.FirstOrDefault(r => r.Name == Parsed.RoleName)?.Id ?? Roles.FirstOrDefault()?.Id ?? 0;
        RoleIds = matchedRole > 0 ? new[] { matchedRole } : Array.Empty<int>();
        ShiftType = Parsed.ShiftType ?? ShiftType.Day;
        EmploymentType = Parsed.EmploymentType ?? EmploymentType.FullTime;
        (StartTime, EndTime) = DefaultTimes(ShiftType);
        ShiftDate = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(1);
        Negotiable = Parsed.PayNegotiable;
        SharePercent = Parsed.SharePercent;
        GenderRequirement = Parsed.Gender;
        if (Parsed.PayAmount is not null) { PayAmount = Parsed.PayAmount; SalaryMin = Parsed.PayAmount; }
        Description = Raw.RawText;
        Title = Parsed.RoleName is not null ? $"استخدام {Parsed.RoleName}" : "موقعیت استخدامی";

        // Talent prefill.
        Phone = Parsed.Phone;
        PersonName = Parsed.PersonName;
        YearsExperience = Parsed.YearsExperience;
        IsLicensed = Parsed.IsLicensed;
        AreaNote = Parsed.AreaNote;
        TalentCityId = Cities.FirstOrDefault(c => c.Name == Parsed.CityName)?.Id
            ?? Cities.FirstOrDefault()?.Id ?? 0;

        // Facility: try to match the listing's facility to one we already have; otherwise
        // prefill the "new facility" box so publishing creates it.
        if (!string.IsNullOrWhiteSpace(Parsed.FacilityName))
        {
            var cityId = await _db.Cities.Where(c => c.Name == Parsed.CityName)
                .Select(c => (int?)c.Id).FirstOrDefaultAsync();
            var match = FacilityMatcher.FindBest(Facilities, Parsed.FacilityName, cityId);
            if (match is not null)
            {
                FacilityId = match.Id;
                Parsed.Notes.Add($"مرکز منطبق در سیستم: «{match.Name}» — همین انتخاب شد.");
            }
            else
            {
                NewFacilityName = Parsed.FacilityName;
                Parsed.Notes.Add($"مرکز جدید پیشنهادی: «{Parsed.FacilityName}» — هنگام انتشار ساخته می‌شود.");
            }
        }
        return Page();
    }

    public async Task<IActionResult> OnPostPublishAsync(int id)
    {
        Raw = await _db.RawListings.FirstOrDefaultAsync(r => r.Id == id);
        if (Raw is null) return NotFound();

        // One or more roles — publish a separate listing per selected role.
        var validRoles = await _db.Roles.Where(r => RoleIds.Contains(r.Id)).ToListAsync();
        if (validRoles.Count == 0)
        {
            Error = "حداقل یک نقش معتبر انتخاب کن.";
            return RedirectToPage(new { id });
        }

        var payType = Negotiable ? PayType.Negotiable
            : (PayAmount is null && SharePercent is not null ? PayType.Percentage : PayType.PerShift);
        var payAmt = Negotiable ? (long?)null : PayAmount;
        var sharePct = Negotiable ? (int?)null : SharePercent;

        // ---- آماده به کار: no facility; one TalentListing per role ----
        if (Kind == ListingKind.Talent)
        {
            var cityId = TalentCityId > 0 && await _db.Cities.AnyAsync(c => c.Id == TalentCityId)
                ? TalentCityId
                : await _db.Cities.OrderByDescending(c => c.IsActive).Select(c => (int?)c.Id).FirstOrDefaultAsync();
            if (cityId is null)
            {
                Error = "شهری برای انتشار آگهی «آماده به کار» موجود نیست.";
                return RedirectToPage(new { id });
            }
            var roleNames = await _db.Roles.Select(r => r.Name).ToListAsync();
            var reparsed = _parser.Parse(Raw.RawText, roleNames, await CityNamesAsync(), await DistrictNamesAsync());
            var contactSpecs = reparsed.Contacts.Select((c, i) => (c.Type, c.Value, Order: i)).ToList();
            var adminPhone = string.IsNullOrWhiteSpace(Phone) ? null : Phone.Trim();
            var tags = string.Join(" ", reparsed.Tags.Distinct());

            // Fresh ContactMethod instances per listing (EF can't share children across parents).
            List<ContactMethod> FreshContacts()
            {
                var list = contactSpecs.Select(s => new ContactMethod { Type = s.Type, Value = s.Value, SortOrder = s.Order }).ToList();
                if (adminPhone is not null)
                {
                    var d = new string(adminPhone.Where(char.IsDigit).ToArray());
                    if (!list.Any(c => new string(c.Value.Where(char.IsDigit).ToArray()) == d))
                        list.Insert(0, new ContactMethod { Type = ContactType.Mobile, Value = adminPhone, SortOrder = -1 });
                }
                return list;
            }

            TalentListing? firstTalent = null;
            foreach (var role in validRoles)
            {
                var t = new TalentListing
                {
                    RoleId = role.Id, CityId = cityId.Value,
                    PersonName = string.IsNullOrWhiteSpace(PersonName) ? null : PersonName.Trim(),
                    YearsExperience = YearsExperience, IsLicensed = IsLicensed,
                    AreaNote = string.IsNullOrWhiteSpace(AreaNote) ? null : AreaNote.Trim(),
                    Availability = EmploymentType, Gender = GenderRequirement,
                    PayType = payType, PayAmount = payAmt, SharePercent = sharePct,
                    Phone = adminPhone, Description = Description,
                    Status = ShiftStatus.Open, Source = ShiftSource.Aggregated, SourceUrl = Raw.SourceUrl,
                    Contacts = FreshContacts(),
                    Tags = string.Join(" ", new[] { tags, role.Name }.Where(x => !string.IsNullOrWhiteSpace(x))),
                };
                _db.TalentListings.Add(t);
                firstTalent ??= t;
            }
            await _db.SaveChangesAsync();
            Raw.Status = RawListingStatus.Normalized;
            Raw.LinkedTalentId = firstTalent!.Id;
            await _db.SaveChangesAsync();
            return RedirectToPage("/Admin/Index");
        }

        // ---- Shift / Job: need a facility (falls back to «نامشخص / ثبت نشده») ----
        var facilityId = await ResolveFacilityIdAsync();
        if (facilityId is null)
        {
            Error = "شهری برای ساخت مرکز موجود نیست؛ ابتدا یک شهر اضافه کن.";
            return RedirectToPage(new { id });
        }
        var many = validRoles.Count > 1;

        if (Kind == ListingKind.Shift)
        {
            var created = new List<Shift>();
            foreach (var role in validRoles)
            {
                var shift = new Shift
                {
                    FacilityId = facilityId.Value, RoleId = role.Id,
                    Date = ShiftDate, StartTime = StartTime, EndTime = EndTime, ShiftType = ShiftType,
                    SpecialtyRequired = role.Name, Description = Description,
                    PayType = payType, PayAmount = payAmt, SharePercent = sharePct,
                    GenderRequirement = GenderRequirement, Status = ShiftStatus.Open,
                    Source = ShiftSource.Aggregated, SourceUrl = Raw.SourceUrl,
                };
                _db.Shifts.Add(shift);
                created.Add(shift);
            }
            await _db.SaveChangesAsync();
            Raw.Status = RawListingStatus.Normalized;
            Raw.LinkedShiftId = created[0].Id;
            await _db.SaveChangesAsync();
            foreach (var s in created) await _notify.NotifyNewShiftAsync(s.Id);
        }
        else
        {
            var created = new List<JobOpening>();
            foreach (var role in validRoles)
            {
                var job = new JobOpening
                {
                    FacilityId = facilityId.Value, RoleId = role.Id,
                    // With several roles, give each a role-specific title; with one, honor the typed title.
                    Title = many || string.IsNullOrWhiteSpace(Title) ? $"استخدام {role.Name}" : Title.Trim(),
                    EmploymentType = EmploymentType,
                    SalaryMin = Negotiable ? null : SalaryMin, SalaryMax = Negotiable ? null : SalaryMax,
                    GenderRequirement = GenderRequirement, Description = Description,
                    Status = ShiftStatus.Open, Source = ShiftSource.Aggregated, SourceUrl = Raw.SourceUrl,
                };
                _db.JobOpenings.Add(job);
                created.Add(job);
            }
            await _db.SaveChangesAsync();
            Raw.Status = RawListingStatus.Normalized;
            await _db.SaveChangesAsync();
            foreach (var j in created) await _notify.NotifyNewJobAsync(j.Id);
        }
        return RedirectToPage("/Admin/Index");
    }

    public async Task<IActionResult> OnPostDiscardAsync(int id)
    {
        var raw = await _db.RawListings.FirstOrDefaultAsync(r => r.Id == id);
        if (raw is null) return NotFound();
        raw.Status = RawListingStatus.Discarded;
        await _db.SaveChangesAsync();
        return RedirectToPage("/Admin/Index");
    }

    private static (TimeOnly, TimeOnly) DefaultTimes(ShiftType t) => t switch
    {
        ShiftType.Day => (new TimeOnly(8, 0), new TimeOnly(14, 0)),
        ShiftType.Evening => (new TimeOnly(14, 0), new TimeOnly(20, 0)),
        ShiftType.Night => (new TimeOnly(20, 0), new TimeOnly(8, 0)),
        _ => (new TimeOnly(8, 0), new TimeOnly(8, 0)),
    };

    /// <summary>Placeholder facility name used when an ad doesn't name a real one.</summary>
    private const string UnknownFacilityName = "نامشخص / ثبت نشده";

    /// <summary>
    /// Returns a valid FacilityId. Prefers the picked facility, then the typed/parsed name
    /// (reusing a fuzzy match before creating), and finally falls back to a single shared
    /// «نامشخص / ثبت نشده» record so publishing never fails for a missing facility.
    /// Returns null only when there are no cities at all.
    /// </summary>
    private async Task<int?> ResolveFacilityIdAsync()
    {
        if (FacilityId > 0 && await _db.Facilities.AnyAsync(f => f.Id == FacilityId))
            return FacilityId;

        var cityId = await _db.Cities.OrderByDescending(c => c.IsActive)
            .Select(c => (int?)c.Id).FirstOrDefaultAsync();
        if (cityId is null) return null;   // no cities seeded — cannot create a facility

        // No facility named in the ad → use/create the shared placeholder.
        var isPlaceholder = string.IsNullOrWhiteSpace(NewFacilityName);
        var name = isPlaceholder ? UnknownFacilityName : NewFacilityName.Trim();

        // Approximate coords carried from the crawl (e.g. Divar). NEVER apply them to the shared
        // «نامشخص» placeholder — it's reused across many ads, so a single ad's point would mislead.
        bool HasGeo() => !isPlaceholder && Raw?.Lat is not null;

        // Reuse an existing facility that's exactly or closely the same (Persian-aware fuzzy
        // match), so we don't create duplicates like «بیمارستان میلاد» vs «میلاد».
        var all = await _db.Facilities.ToListAsync();
        var match = FacilityMatcher.FindBest(all, name, cityId);
        if (match is not null)
        {
            if (HasGeo() && match.Lat is null && match.Lng is null)   // backfill only, never overwrite
            {
                match.Lat = Raw!.Lat; match.Lng = Raw.Lng;
                await _db.SaveChangesAsync();
            }
            return match.Id;
        }

        var facility = new Facility
        {
            Name = name,
            CityId = cityId.Value,
            Type = FacilityType.Hospital,
            Verification = VerificationStatus.Unverified,
            IsVerified = false,
            Lat = HasGeo() ? Raw!.Lat : null,
            Lng = HasGeo() ? Raw!.Lng : null,
        };
        _db.Facilities.Add(facility);
        await _db.SaveChangesAsync();
        return facility.Id;
    }

    private async Task LoadListsAsync()
    {
        Facilities = await _db.Facilities.Include(f => f.City).OrderBy(f => f.Name).ToListAsync();
        Roles = await _db.Roles.Where(r => r.IsActive).OrderBy(r => r.SortOrder).ToListAsync();
        Cities = await _db.Cities.Where(c => c.IsActive).OrderBy(c => c.Name).ToListAsync();
    }

    private Task<List<string>> CityNamesAsync() => _db.Cities.Select(c => c.Name).ToListAsync();
    private Task<List<string>> DistrictNamesAsync() => _db.Districts.Select(d => d.Name).ToListAsync();
}
