namespace Baya.Infrastructure.CrossCutting.Seams;

/// <summary>
/// Options bound from the <c>Seams</c> configuration section. The mock seams read non-secret defaults
/// from here; production keys/paths come from environment variables or user-secrets, never committed.
///
/// <para><b>Provider selection (refinement-phase-8).</b> Each vendor rail carries a <c>Provider</c> selector
/// (default = the mock, so an unconfigured environment behaves exactly as before). Setting it to a real
/// provider token (e.g. <c>Seams:Sms:Provider = kavenegar</c>) swaps in the real HTTP adapter behind the same
/// contract — handlers never change. This makes a <b>partial rollout</b> the normal case: real SMS + real
/// geocoder while payments stay mocked in a pre-launch environment is just three config keys.</para>
/// </summary>
public sealed class SeamOptions
{
    public const string SectionName = "Seams";

    public FieldEncryptionOptions FieldEncryption { get; set; } = new();
    public SmsOptions Sms { get; set; } = new();
    public ObjectStorageOptions ObjectStorage { get; set; } = new();
    public BankOwnershipOptions BankOwnership { get; set; } = new();
    public GeocodingOptions Geocoding { get; set; } = new();
    public ShahkarOptions Shahkar { get; set; } = new();
    public IdentityKycOptions IdentityKyc { get; set; } = new();
    public PaymentCaptureOptions PaymentCapture { get; set; } = new();
    public PaymentsOptions Payments { get; set; } = new();
    public MoadianOptions Moadian { get; set; } = new();
    public BnplOptions Bnpl { get; set; } = new();
    public CurrencyOptions Currency { get; set; } = new();
    public BankTransferOptions BankTransfer { get; set; } = new();
    public ReviewModerationOptions ReviewModeration { get; set; } = new();
    public LicenseVerificationOptions LicenseVerification { get; set; } = new();
    public FinnotechOptions Finnotech { get; set; } = new();
}

/// <summary>
/// Shared credentials for the Finnotech-class KYC bridge that fronts three trust rails — شاهکار
/// (<c>IShahkarVerifier</c>), e-KYC (<c>IIdentityKycProvider</c>), and استعلام شبا
/// (<c>IBankAccountOwnershipVerifier</c>). Each seam opts in with its own <c>Provider = finnotech</c> selector,
/// but they authenticate against the same tenant, so the connection facts live here once. All values are
/// secrets — user-secrets / environment, never committed.
/// </summary>
public sealed class FinnotechOptions
{
    /// <summary>API host (defaults to Finnotech's public sandbox/production host at the adapter).</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>The tenant's client id (<c>NID</c>) — the Finnotech app identifier.</summary>
    public string ClientId { get; set; } = string.Empty;

    /// <summary>A pre-issued bearer access token (client-credential token exchange is out of scope for the MVP
    /// adapter; a deployment supplies a current token, refreshed out-of-band).</summary>
    public string AccessToken { get; set; } = string.Empty;
}

/// <summary>Stable provider tokens for the <c>Provider</c> selectors, so a typo fails closed to the mock.</summary>
public static class SeamProviders
{
    public const string Mock = "mock";
    public const string LocalDisk = "local";

    // SMS gateways
    public const string Kavenegar = "kavenegar";
    public const string SmsIr = "smsir";
    public const string Ghasedak = "ghasedak";

    // Object storage
    public const string S3 = "s3";

    // Trust / identity (a Finnotech-class KYC bridge fronts Shahkar / e-KYC / استعلام شبا)
    public const string Finnotech = "finnotech";

    // Geocoding
    public const string Neshan = "neshan";

    // Card PSP acquirers
    public const string ZarinPal = "zarinpal";
    public const string Sadad = "sadad";
    public const string Vandar = "vandar";
    public const string Jibit = "jibit";

    // BNPL
    public const string SnappPay = "snapppay";
    public const string Digipay = "digipay";

    // e-invoicing
    public const string Moadian = "moadian";
}

/// <summary>
/// The outbound SMS rail (<c>ISmsSender</c>). <see cref="Provider"/> = <c>mock</c> logs the OTP (the b2
/// <c>LoggingSmsSender</c>); set it to <c>kavenegar</c> / <c>smsir</c> / <c>ghasedak</c> to deliver over a real
/// Iranian gateway. <b>refinement-phase-8:</b> when a real provider is selected the Development OTP-in-logs/echo
/// bridge is disabled — the OTP must never be logged once real SMS ships.
/// </summary>
public sealed class SmsOptions
{
    /// <summary><c>mock</c> (default) | <c>kavenegar</c> | <c>smsir</c> | <c>ghasedak</c>.</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>Gateway API key / token (secret — user-secrets or environment, never committed).</summary>
    public string ApiKey { get; set; } = string.Empty;

    /// <summary>The registered sender line (used by <c>SendAsync</c> free-form messages and non-template sends).</summary>
    public string SenderLine { get; set; } = string.Empty;

    /// <summary>Override the gateway base URL (defaults to the provider's public API host).</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>The approved OTP template/pattern name the gateway sends the code through (verify-lookup APIs).</summary>
    public string OtpTemplate { get; set; } = string.Empty;
}

/// <summary>
/// Tunes the mock <c>ILicenseVerificationService</c> (b15 partner-center eNamad / MoH establishment-permit
/// check). By default every check returns <c>NeedsManualReview</c> so <c>VerifyPartnerCenter</c> records the
/// human admin decision. Set <see cref="AutoApprove"/> to have the mock return <c>Valid</c> (test the
/// auto-approve path). The real eNamad / MoH registry adapter ignores these knobs.
/// </summary>
public sealed class LicenseVerificationOptions
{
    /// <summary>When true, permit/eNamad checks auto-approve (return <c>Valid</c>) instead of requiring a manual decision.</summary>
    public bool AutoApprove { get; set; }
}

/// <summary>
/// Tunes the mock <c>IReviewModerationService</c> (b14 AI review pre-screen). By default clean text returns a
/// human-review <c>Flag</c> (keeping the publish gate on); a banned-word hit returns <c>Reject</c>. Set
/// <see cref="AutoApproveClean"/> to have clean text auto-<c>Approve</c> (auto-publish). The real text
/// classifier / LLM endpoint ignores these knobs.
/// </summary>
public sealed class ReviewModerationOptions
{
    /// <summary>When true, clean text is auto-approved (auto-published) instead of flagged for human review.</summary>
    public bool AutoApproveClean { get; set; }

    /// <summary>Case-insensitive substrings that mark a review for rejection.</summary>
    public List<string> BannedWords { get; set; } = ["scam", "fraud", "کلاهبردار"];
}

/// <summary>
/// Tunes the mock <c>IBankTransferProvider</c> (b13 PAYA/SATNA payouts). By default every instruction settles
/// paid with a deterministic transfer reference and no money moves. Set <see cref="ForceFailure"/> to fail the
/// whole batch (→ <c>failed</c>) or <see cref="FailIban"/> to fail just one destination (→ <c>partially_failed</c>,
/// so the retry path is testable). The real transferor ignores these — the source settlement account, per-nurse
/// Sheba, and the reconciliation callback come from provider config.
/// </summary>
public sealed class BankTransferOptions
{
    /// <summary><c>mock</c> (default) | <c>jibit</c> | <c>vandar</c> | <c>sadad</c> — the payout transferor.</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>Transferor API base URL.</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>Transferor API key / bearer token (secret).</summary>
    public string ApiKey { get; set; } = string.Empty;

    /// <summary>The platform's registered <b>source settlement account</b> the batch debits (IBAN/account id the
    /// transferor recognises). Every PAYA/SATNA transfer originates here.</summary>
    public string SourceSettlementAccount { get; set; } = string.Empty;

    /// <summary>When true, every payout instruction is rejected so the whole-batch-failure path is testable.</summary>
    public bool ForceFailure { get; set; }

    /// <summary>A designated IBAN that is rejected while others succeed — exercises the <c>partially_failed</c>
    /// batch outcome and the single-payout retry.</summary>
    public string FailIban { get; set; } = string.Empty;
}

/// <summary>
/// Tunes the mock <c>ICurrencyNormalizer</c> (b12). Toman → IRR multiplies by <see cref="TomanToIrrMultiplier"/>
/// (10); IRR passes through. The real adapter reads the multiplier from provider config. Conversion happens only
/// at the provider boundary, never internally.
/// </summary>
public sealed class CurrencyOptions
{
    /// <summary>How many Rial one Toman is (10). A currency redenomination is a config change, not a code change.</summary>
    public long TomanToIrrMultiplier { get; set; } = 10;
}

/// <summary>
/// Tunes the mock <c>IMoadianClient</c> (b11 e-invoicing). By default a submission stays <c>pending</c> with no
/// reference. Set <see cref="ForceRegistered"/> to make it return <c>registered</c> with a fake 22-digit ref so
/// the reconciliation/registered path is testable. The real سامانه مودیان adapter ignores these.
/// </summary>
public sealed class MoadianOptions
{
    /// <summary><c>mock</c> (default) | <c>moadian</c> — the real سامانه مودیان submission adapter.</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>مودیان API base URL (the tax-authority endpoint).</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>The platform's مودیان memory/economic id (<c>memoryId</c> / شناسه یکتای حافظه مالیاتی).</summary>
    public string MemoryId { get; set; } = string.Empty;

    /// <summary>A pre-issued bearer token for the مودیان API (the signing-certificate token exchange is a
    /// deploy-time concern; a deployment supplies a current token). Secret.</summary>
    public string AccessToken { get; set; } = string.Empty;

    /// <summary>When true, a submission returns <c>registered</c> + a deterministic fake 22-digit reference.</summary>
    public bool ForceRegistered { get; set; }
}

/// <summary>
/// Tunes the deterministic mock <c>IBnplProvider</c> (b12, superset of the b11 revert-only stub). The mock
/// commission % drives the settle (net = order − commission); the real adapter reads the actual deducted amount
/// from each settlement and ignores these knobs.
/// </summary>
public sealed class BnplOptions
{
    /// <summary><c>mock</c> (default) | <c>real</c> — when <c>real</c>, <c>IBnplProviderResolver</c> maps each
    /// <c>provider_code</c> to its concrete adapter (SnappPay / Digipay) instead of the one mock.</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>Per-provider connection facts, keyed by <c>provider_code</c> (<c>snapppay</c>/<c>digipay</c>).
    /// Credentials proper (client id/secret) come from the encrypted <c>payment_gateways.config_json</c> in a
    /// full deployment; the base URL + non-secret facts can be defaulted here.</summary>
    public Dictionary<string, BnplProviderConnection> Providers { get; set; } = new(StringComparer.OrdinalIgnoreCase);

    /// <summary>The currency the BNPL providers speak on the wire (<c>TOMAN</c> or <c>IRR</c>); conversion to IRR
    /// happens only at the adapter boundary via <c>ICurrencyNormalizer</c>. SnappPay/Digipay speak Rial.</summary>
    public string WireCurrency { get; set; } = "IRR";

    /// <summary>When true, token/revert/update/cancel all fail so the provider-declined paths are testable.</summary>
    public bool ForceFailure { get; set; }

    /// <summary>When true, the mock reports the provider returned its commission (a non-null, zero reversal
    /// placeholder) so the <c>provider_commission_reversed_amount</c> reconciliation is exercised.</summary>
    public bool ReverseProviderCommission { get; set; }

    /// <summary>The mock provider merchant commission rate (fraction) the settle deducts. Read from the response
    /// by the handler, never hardcoded there; the real adapter reads the true per-contract deducted amount.</summary>
    public decimal CommissionRate { get; set; } = 0.10m;

    /// <summary>When true the mock settle stamps <c>settled_at = now</c>; false models the non-instant
    /// (deferred/T+1–3/weekly) settlement with a null <c>settled_at</c>.</summary>
    public bool SettlementInstant { get; set; } = true;

    /// <summary>The provider credit ceiling (IRR); an order above it returns <c>ceiling_exceeded</c>.</summary>
    public long CreditCeilingIrr { get; set; } = 2_000_000_000;

    /// <summary>A designated test mobile that returns <c>not_eligible</c> so the fall-back-to-card path is testable.</summary>
    public string NotEligibleMobile { get; set; } = "09120000099";
}

/// <summary>Non-secret connection facts for one BNPL provider (base URL, sandbox flag, merchant handle). The
/// secret client id/secret live in the encrypted <c>payment_gateways.config_json</c>; a real adapter reads both.</summary>
public sealed class BnplProviderConnection
{
    public string BaseUrl { get; set; } = string.Empty;
    public bool Sandbox { get; set; }

    /// <summary>Optional non-secret merchant/terminal identifier the provider expects on requests.</summary>
    public string MerchantId { get; set; } = string.Empty;
}

/// <summary>
/// Tunes the b10 money-path mocks (PSP acquirer, تسهیم split, webhook verifier). The real adapters ignore
/// these — production merchant ids / signing keys come from <c>payment_gateways.config_json</c> and secrets,
/// never from here.
/// </summary>
public sealed class PaymentsOptions
{
    /// <summary><c>mock</c> (default) | <c>zarinpal</c> | <c>sadad</c> | <c>vandar</c> | <c>jibit</c> — the card
    /// acquirer <c>IPaymentProvider</c> + <c>ISettlementSplitProvider</c> + <c>IWebhookVerifier</c> swap together.</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>Acquirer IPG base URL (the payment-request / verify / refund host).</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>The acquirer merchant id / terminal (non-secret handle). Production reads it from the encrypted
    /// <c>payment_gateways.config_json</c>; this default enables a single-merchant deployment without the DB row.</summary>
    public string MerchantId { get; set; } = string.Empty;

    /// <summary>Where the acquirer sends the customer back after the hosted payment page (the return deep-link the
    /// adapter passes as the callback URL when opening the IPG session).</summary>
    public string CallbackUrl { get; set; } = string.Empty;

    /// <summary>Per-provider webhook signing secret (HMAC key), keyed by <c>provider_code</c>. The real
    /// <c>IWebhookVerifier</c> verifies the raw callback body against this; a provider with no signature falls back
    /// to the mandatory server-side <c>verify</c> re-check.</summary>
    public Dictionary<string, string> WebhookSigningSecrets { get; set; } = new(StringComparer.OrdinalIgnoreCase);

    /// <summary>The header the provider carries its signature in (default <c>X-Signature</c>).</summary>
    public string SignatureHeader { get; set; } = "X-Signature";

    /// <summary>The platform's own registered IBAN (SHEBA) the split credits the commission leg to.</summary>
    public string PlatformSheba { get; set; } = "IR000000000000000000000001";

    /// <summary>A callback whose raw body contains this marker is treated as an <b>invalid signature</b> by the
    /// mock verifier, so the "unverified callback mutates nothing" path is testable.</summary>
    public string InvalidSignatureMarker { get; set; } = "INVALID_SIGNATURE";
}

/// <summary>
/// Tunes the mock <c>IPaymentCaptureSimulator</c> (backend-phase-9). By default every capture succeeds with
/// a fake gateway reference and <see cref="PspFeeAmount"/>. Set <see cref="ForceFailure"/> to exercise the
/// "capture failed → no booking" path. The real b10 card capture ignores these.
/// </summary>
public sealed class PaymentCaptureOptions
{
    /// <summary>When true, every capture returns a failed result so conversion refuses to create a booking.</summary>
    public bool ForceFailure { get; set; }

    /// <summary>The PSP/gateway fee (IRR) the mock reports on a successful capture.</summary>
    public long? PspFeeAmount { get; set; }
}

/// <summary>
/// Tunes the mock <c>IShahkarVerifier</c> (phone↔national-id binding). A submitted phone equal to
/// <see cref="SharedSimPhone"/> returns the explicit shared-SIM failure; a national id equal to
/// <see cref="MismatchNationalId"/> returns a plain mismatch; every other pair matches. The real vendor
/// implementation ignores these.
/// </summary>
public sealed class ShahkarOptions
{
    /// <summary><c>mock</c> (default) | <c>finnotech</c> — the real شاهکار bridge (shares <c>Seams:Finnotech</c> creds).</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>The designated test phone that returns the shared-SIM failure state.</summary>
    public string SharedSimPhone { get; set; } = "09120000000";

    /// <summary>The designated test national id that returns a plain phone↔national-id mismatch.</summary>
    public string MismatchNationalId { get; set; } = "1111111111";
}

/// <summary>
/// Tunes the mock <c>IIdentityKycProvider</c>. A national id equal to <see cref="FailNationalId"/> fails
/// KYC; every other (well-formed) national id passes with <see cref="MatchedName"/>. The real e-KYC vendor
/// implementation ignores these.
/// </summary>
public sealed class IdentityKycOptions
{
    /// <summary><c>mock</c> (default) | <c>finnotech</c> — the real e-KYC bridge (shares <c>Seams:Finnotech</c> creds).</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>The designated test national id that fails identity KYC.</summary>
    public string FailNationalId { get; set; } = "0000000000";

    /// <summary>The name the mock reports as matched on a passing KYC (informational; the authoritative
    /// identity name for credential cross-check comes from the users row).</summary>
    public string MatchedName { get; set; } = "Verified Nurse";
}

/// <summary>
/// Tunes the mock <c>IGeocoder</c>. By default it resolves every address to a deterministic point around
/// the city centroid. Set <see cref="ReturnNullCoordinates"/> to force the unresolved path globally, or
/// embed <see cref="LowConfidenceMarker"/> in a single address to exercise the "saved without a map pin"
/// UI state per-request. The real vendor implementation ignores these.
/// </summary>
public sealed class GeocodingOptions
{
    /// <summary><c>mock</c> (default) | <c>neshan</c> — the real Neshan geocoding adapter.</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>Neshan API key (secret). The real geocoder sends it as the <c>Api-Key</c> header.</summary>
    public string ApiKey { get; set; } = string.Empty;

    /// <summary>Neshan API base URL (defaults to the public host at the adapter).</summary>
    public string BaseUrl { get; set; } = string.Empty;

    /// <summary>When true, every geocode returns null coordinates with low confidence.</summary>
    public bool ReturnNullCoordinates { get; set; }

    /// <summary>An address whose text contains this marker resolves to null coordinates (testability).</summary>
    public string LowConfidenceMarker { get; set; } = "NO_GEO";

    /// <summary>Confidence returned for a successfully resolved address.</summary>
    public double ResolvedConfidence { get; set; } = 0.9;
}

/// <summary>
/// Tunes the mock <c>IBankAccountOwnershipVerifier</c> (استعلام شبا). A submitted IBAN equal to
/// <see cref="MismatchIban"/> returns an ownership mismatch so the payout-gating path is testable; every
/// other IBAN returns a match. The real vendor implementation ignores these.
/// </summary>
public sealed class BankOwnershipOptions
{
    /// <summary><c>mock</c> (default) | <c>finnotech</c> — the real استعلام شبا bridge (shares <c>Seams:Finnotech</c> creds).</summary>
    public string Provider { get; set; } = SeamProviders.Mock;

    /// <summary>The designated test IBAN that returns <c>matched_national_id = false</c>.</summary>
    public string MismatchIban { get; set; } = "IR000000000000000000000000";

    /// <summary>The account-holder name the mock echoes back for a matching inquiry.</summary>
    public string MatchedHolderName { get; set; } = "Verified Account Holder";

    /// <summary>The account-holder name the mock returns for the mismatch IBAN.</summary>
    public string MismatchHolderName { get; set; } = "Unmatched Account Holder";
}

public sealed class FieldEncryptionOptions
{
    /// <summary>Base64 32-byte AES key. Local-dev default only; override per environment.</summary>
    public string Key { get; set; } = string.Empty;

    /// <summary>Base64 HMAC key used for deterministic lookup hashes.</summary>
    public string HashKey { get; set; } = string.Empty;
}

public sealed class ObjectStorageOptions
{
    /// <summary><c>local</c> (default) | <c>s3</c> — S3/MinIO/ArvanCloud object storage with presigned PUT/GET.</summary>
    public string Provider { get; set; } = SeamProviders.LocalDisk;

    /// <summary>Filesystem root the local-disk mock writes blobs under.</summary>
    public string RootPath { get; set; } = string.Empty;

    /// <summary>S3-compatible endpoint host, e.g. <c>https://s3.ir-thr-at1.arvanstorage.ir</c> or a MinIO URL.</summary>
    public string ServiceUrl { get; set; } = string.Empty;

    /// <summary>The bucket blobs are stored in.</summary>
    public string Bucket { get; set; } = string.Empty;

    /// <summary>The S3 region (SigV4 credential scope; MinIO/ArvanCloud commonly use <c>us-east-1</c> or their own).</summary>
    public string Region { get; set; } = "us-east-1";

    /// <summary>S3 access key id (secret).</summary>
    public string AccessKey { get; set; } = string.Empty;

    /// <summary>S3 secret access key (secret).</summary>
    public string SecretKey { get; set; } = string.Empty;

    /// <summary>Use path-style addressing (<c>{endpoint}/{bucket}/{key}</c>) — required by MinIO/ArvanCloud; AWS
    /// proper uses virtual-host style. Default true (path-style) since Iranian S3 endpoints expect it.</summary>
    public bool UsePathStyle { get; set; } = true;

    /// <summary>How long a presigned GET/PUT URL stays valid (seconds).</summary>
    public int PresignExpirySeconds { get; set; } = 900;
}
