import Foundation

/// The effective site configuration loaded from the CMP API.
///
/// Maps to the response of `GET {apiBase}/api/v1/config/sites/{siteId}/effective`.
/// The configuration drives banner display, theming, blocking mode, and consent expiry.
public struct ConsentConfig: Codable, Sendable {

    // MARK: - Top-level Fields

    /// Unique identifier for the site.
    public let siteId: String

    /// Human-readable name for the site.
    public let siteName: String

    /// The blocking mode determining the default consent model.
    public let blockingMode: BlockingMode

    /// Consent validity in days. After expiry the banner is shown again.
    public let consentExpiryDays: Int

    /// The current version of this configuration.
    /// Stored alongside consent records to detect when re-consent is needed.
    public let bannerVersion: String

    /// Banner display and theming configuration.
    public let bannerConfig: BannerConfig

    /// Available categories for this site.
    public let categories: [CategoryConfig]

    // MARK: - Initialiser

    public init(
        siteId: String,
        siteName: String,
        blockingMode: BlockingMode,
        consentExpiryDays: Int,
        bannerVersion: String,
        bannerConfig: BannerConfig,
        categories: [CategoryConfig]
    ) {
        self.siteId = siteId
        self.siteName = siteName
        self.blockingMode = blockingMode
        self.consentExpiryDays = consentExpiryDays
        self.bannerVersion = bannerVersion
        self.bannerConfig = bannerConfig
        self.categories = categories
    }

    // MARK: - Blocking Mode

    /// The consent model applied to visitors.
    public enum BlockingMode: String, Codable, Sendable {
        /// User must opt in before non-essential scripts run (GDPR default).
        case optIn = "opt_in"
        /// Non-essential scripts run by default; user may opt out (CCPA default).
        case optOut = "opt_out"
        /// Informational notice only; no blocking.
        case informational
    }

    // MARK: - Banner Configuration

    /// Visual and behavioural configuration for the consent banner.
    public struct BannerConfig: Codable, Sendable {
        /// Display mode controlling the banner layout.
        public let displayMode: DisplayMode

        /// Primary background colour as a hex string (e.g. `"#FFFFFF"`).
        public let backgroundColor: String?

        /// Primary text colour as a hex string.
        public let textColor: String?

        /// Accent colour used for buttons and highlights.
        public let accentColor: String?

        /// Text for the "Accept all" button.
        public let acceptButtonText: String?

        /// Text for the "Reject all" button.
        public let rejectButtonText: String?

        /// Text for the "Manage preferences" button.
        public let manageButtonText: String?

        /// The banner title text.
        public let title: String?

        /// The banner body copy.
        public let description: String?

        /// URL for the site's privacy policy.
        public let privacyPolicyUrl: String?

        public enum DisplayMode: String, Codable, Sendable {
            case overlay
            case bottomBanner = "bottom_banner"
            case topBanner = "top_banner"
            case cornerPopup = "corner_popup"
            case inline
        }

        // Default-value initialiser used in tests and previews
        public init(
            displayMode: DisplayMode = .bottomBanner,
            backgroundColor: String? = nil,
            textColor: String? = nil,
            accentColor: String? = nil,
            acceptButtonText: String? = nil,
            rejectButtonText: String? = nil,
            manageButtonText: String? = nil,
            title: String? = nil,
            description: String? = nil,
            privacyPolicyUrl: String? = nil
        ) {
            self.displayMode = displayMode
            self.backgroundColor = backgroundColor
            self.textColor = textColor
            self.accentColor = accentColor
            self.acceptButtonText = acceptButtonText
            self.rejectButtonText = rejectButtonText
            self.manageButtonText = manageButtonText
            self.title = title
            self.description = description
            self.privacyPolicyUrl = privacyPolicyUrl
        }
    }

    // MARK: - Category Configuration

    /// Per-category configuration as returned by the API.
    public struct CategoryConfig: Codable, Sendable {
        /// Machine-readable category key (matches ``ConsentCategory`` raw values).
        public let key: String
        /// Whether this category is enabled for this site.
        public let enabled: Bool
        /// Overridden display name (falls back to ``ConsentCategory/displayName`` if absent).
        public let displayName: String?
        /// Overridden description text.
        public let description: String?

        public init(key: String, enabled: Bool, displayName: String?, description: String?) {
            self.key = key
            self.enabled = enabled
            self.displayName = displayName
            self.description = description
        }

        /// Resolves to the matching ``ConsentCategory``, if the key is recognised.
        public var category: ConsentCategory? {
            ConsentCategory(rawValue: key)
        }
    }

    // MARK: - Convenience

    /// Returns only the enabled ``ConsentCategory`` values for this config.
    public var enabledCategories: [ConsentCategory] {
        categories.compactMap { cfg in
            guard cfg.enabled else { return nil }
            return cfg.category
        }
    }
}

// MARK: - Cached Config Wrapper

/// Wraps a ``ConsentConfig`` with metadata needed for cache invalidation.
public struct CachedConfig: Codable {
    public let config: ConsentConfig
    public let fetchedAt: Date

    /// The cache TTL in seconds (10 minutes by default).
    public static let ttl: TimeInterval = 600

    public var isExpired: Bool {
        Date().timeIntervalSince(fetchedAt) > CachedConfig.ttl
    }

    public init(config: ConsentConfig, fetchedAt: Date) {
        self.config = config
        self.fetchedAt = fetchedAt
    }
}
