using BigRegister.Api.Contracts;

namespace BigRegister.Api.Data;

/// <summary>
/// Organization template per sub-organization (WP-23, Brief v2 PRD §3): one row per
/// sub-org. `Draft` is the work-in-progress payload (Version 0), `History` the
/// append-only list of published snapshots, `PublishedVersion` points into it.
/// Rollback copies an old snapshot back into the draft — it never rewrites history.
/// Mirrors <see cref="BriefStore"/>: static class, short-lived context per call,
/// nested DTO shapes stored as JSON text columns (WP-22 posture).
/// </summary>
public sealed class OrgTemplateEntity
{
  public required string SubOrgId { get; init; }
  public OrgTemplateDto Draft { get; set; } = null!;
  public int PublishedVersion { get; set; }
  public List<OrgTemplateVersionDto> History { get; set; } = new();
}

/// <summary>ponytail: one global lock, same shape as BriefStore — SQLite tolerates
/// only one writer at a time anyway.</summary>
public static class OrgTemplateStore
{
  private static readonly object _gate = new();

  public static IReadOnlyList<SubOrgSummaryDto> List()
  {
    lock (_gate)
    {
      using var db = Seeded();
      return db.OrgTemplates.AsEnumerable()
          .Select(e => new SubOrgSummaryDto(e.SubOrgId, Published(e).OrgName, e.PublishedVersion))
          .OrderBy(s => s.SubOrgId)
          .ToList();
    }
  }

  public static OrgTemplateAdminViewDto? AdminView(string subOrgId)
  {
    lock (_gate)
    {
      using var db = Seeded();
      var e = db.OrgTemplates.FirstOrDefault(t => t.SubOrgId == subOrgId);
      return e is null ? null : ToAdminView(db, e);
    }
  }

  /// Save the draft. Caller validates first (OrgTemplateRules); null = unknown sub-org.
  public static OrgTemplateAdminViewDto? SaveDraft(string subOrgId, OrgTemplateDto draft)
  {
    lock (_gate)
    {
      using var db = Seeded();
      var e = db.OrgTemplates.FirstOrDefault(t => t.SubOrgId == subOrgId);
      if (e is null) return null;
      // Normalize identity fields the client can't be trusted with: the row key and
      // the draft marker (Version 0) always win over whatever was posted.
      e.Draft = draft with { SubOrgId = subOrgId, Version = 0 };
      db.SaveChanges();
      return ToAdminView(db, e);
    }
  }

  /// Draft → published: append a snapshot to history, advance the pointer. Returns
  /// the new version + how many unsent briefs of this sub-org it re-themes.
  public static PublishOrgTemplateResponse? Publish(string subOrgId, string at)
  {
    lock (_gate)
    {
      using var db = Seeded();
      var e = db.OrgTemplates.FirstOrDefault(t => t.SubOrgId == subOrgId);
      if (e is null) return null;
      var version = e.PublishedVersion + 1;
      // Reassign (not mutate) the list: the JSON ValueConverter has no ValueComparer,
      // so EF only sees the change when the property reference changes.
      e.History = new List<OrgTemplateVersionDto>(e.History)
      {
        new(version, at, e.Draft with { Version = version }),
      };
      e.PublishedVersion = version;
      db.SaveChanges();
      return new PublishOrgTemplateResponse(version, CountUnsent(db, subOrgId));
    }
  }

  /// Rollback = copy an old published snapshot into the draft (admin republishes to
  /// make it live). History stays append-only. Null = unknown sub-org or version.
  public static OrgTemplateAdminViewDto? Rollback(string subOrgId, int version)
  {
    lock (_gate)
    {
      using var db = Seeded();
      var e = db.OrgTemplates.FirstOrDefault(t => t.SubOrgId == subOrgId);
      var old = e?.History.FirstOrDefault(h => h.Version == version);
      if (e is null || old is null) return null;
      e.Draft = old.Template with { Version = 0 };
      db.SaveChanges();
      return ToAdminView(db, e);
    }
  }

  /// The template a brief renders with: the pinned version once sent (immutable
  /// letters), else the sub-org's current published version.
  public static OrgTemplateDto TemplateForBrief(string subOrgId, int? pinnedVersion)
  {
    lock (_gate)
    {
      using var db = Seeded();
      // ponytail: briefs from before this WP carry an empty SubOrgId — fall back to
      // the first seeded sub-org instead of failing the whole screen.
      var e = db.OrgTemplates.FirstOrDefault(t => t.SubOrgId == subOrgId)
          ?? db.OrgTemplates.First(t => t.SubOrgId == OrgTemplateSeed.Registers);
      var pinned = pinnedVersion is { } v ? e.History.FirstOrDefault(h => h.Version == v)?.Template : null;
      return pinned ?? Published(e);
    }
  }

  public static int PublishedVersionOf(string subOrgId)
  {
    lock (_gate)
    {
      using var db = Seeded();
      var e = db.OrgTemplates.FirstOrDefault(t => t.SubOrgId == subOrgId)
          ?? db.OrgTemplates.First(t => t.SubOrgId == OrgTemplateSeed.Registers);
      return e.PublishedVersion;
    }
  }

  /// Test seam: drop all rows so the next call re-seeds.
  public static void Reset()
  {
    lock (_gate)
    {
      using var db = Db.Create();
      db.OrgTemplates.RemoveRange(db.OrgTemplates);
      db.SaveChanges();
    }
  }

  private static OrgTemplateDto Published(OrgTemplateEntity e) =>
      e.History.First(h => h.Version == e.PublishedVersion).Template;

  private static OrgTemplateAdminViewDto ToAdminView(AppDbContext db, OrgTemplateEntity e) =>
      new(e.Draft, e.PublishedVersion, e.History, CountUnsent(db, e.SubOrgId));

  // Status is a JSON column (not translatable to SQL) — count in memory; the demo
  // holds a handful of briefs at most.
  private static int CountUnsent(AppDbContext db, string subOrgId) =>
      db.Briefs.AsEnumerable().Count(b => b.SubOrgId == subOrgId && b.Status.Tag != "sent");

  private static AppDbContext Seeded()
  {
    var db = Db.Create();
    if (!db.OrgTemplates.Any())
    {
      db.OrgTemplates.AddRange(OrgTemplateSeed.All());
      db.SaveChanges();
    }
    return db;
  }
}

/// <summary>
/// Two seeded sub-organizations so the admin editor can demonstrate isolation
/// (editing one never touches the other). Values come from the fictitious sample
/// artifact `voorbeeldbrief-inschrijving.pdf`; each starts with draft == published v1.
/// </summary>
public static class OrgTemplateSeed
{
  public const string Registers = "cibg-registers";
  public const string Vakbekwaamheid = "cibg-vakbekwaamheid";

  // Deterministic seed timestamp (stable golden files, stable demos).
  private const string SeededAt = "2026-07-01T00:00:00.0000000+00:00";

  public static IEnumerable<OrgTemplateEntity> All() => new[]
  {
    Entity(new OrgTemplateDto(
        Registers, "BIG-register",
        "Retouradres: Postbus 00000, 2500 AA Den Haag",
        LogoDocumentId: null,
        "BIG-register · Postbus 00000, 2500 AA Den Haag · 070 000 00 00 · info@voorbeeld.example",
        "Dit is een gegenereerd voorbeelddocument uit de register-reference PoC. Alle gegevens zijn fictief.",
        "A. de Vries", "Hoofd Registratie, BIG-register", "Met vriendelijke groet,",
        new MarginsDto(25, 20, 20, 25))),
    Entity(new OrgTemplateDto(
        Vakbekwaamheid, "CIBG Vakbekwaamheid",
        "Retouradres: Postbus 11111, 2500 BB Den Haag",
        LogoDocumentId: null,
        "CIBG Vakbekwaamheid · Postbus 11111, 2500 BB Den Haag · 070 111 11 11 · vakbekwaamheid@voorbeeld.example",
        "Dit is een gegenereerd voorbeelddocument uit de register-reference PoC. Alle gegevens zijn fictief.",
        "M. Bakker", "Hoofd Vakbekwaamheid, CIBG", "Met vriendelijke groet,",
        new MarginsDto(25, 20, 20, 25))),
  };

  private static OrgTemplateEntity Entity(OrgTemplateDto v1) => new()
  {
    SubOrgId = v1.SubOrgId,
    Draft = v1 with { Version = 0 },
    PublishedVersion = 1,
    History = new() { new OrgTemplateVersionDto(1, SeededAt, v1 with { Version = 1 }) },
  };
}
