import Foundation

// MARK: - Legacy Event (keeping for backward compatibility)

/**
 * Analytics event sent to BigCrunch backend
 *
 * All ad-related events (impressions, clicks, revenue) are tracked and sent
 * to the analytics endpoint in this format.
 */
internal struct AdEvent: Codable {
    let eventType: String // "screen_view", "ad_request", "ad_impression", "ad_revenue", "ad_click", "ad_viewable"
    let placementId: String?
    let format: String? // "banner", "interstitial", "rewarded"
    let timestamp: Int64
    let sessionId: String
    let deviceInfo: DeviceInfo
    let revenue: RevenueData?
}

/**
 * Device and app information included with every event
 */
internal struct DeviceInfo: Codable {
    let platform: String
    let osVersion: String
    let appVersion: String
    let deviceId: String
}

/**
 * Revenue data from ILRD (Impression-Level Revenue Data)
 *
 * Only present in ad_revenue events. Values are in micros (1/1,000,000 of currency unit).
 */
internal struct RevenueData: Codable {
    let valueMicros: Int64
    let currency: String
}

// MARK: - Screen View Options

/**
 * Options for customizing screen view tracking
 *
 * Allows app developers to provide page URL, content metadata,
 * and custom dimensions for analytics events.
 */
public struct ScreenViewOptions {
    /// Override the auto-generated page URL (must be a valid URL)
    public var pageUrl: String?

    /// Content metadata for this screen/page
    public var pageMeta: PageMetaData?

    /// Custom key-value dimensions attached to analytics events
    public var customDimensions: [String: String]?

    public init(
        pageUrl: String? = nil,
        pageMeta: PageMetaData? = nil,
        customDimensions: [String: String]? = nil
    ) {
        self.pageUrl = pageUrl
        self.pageMeta = pageMeta
        self.customDimensions = customDimensions
    }
}

/**
 * Content metadata for a screen/page view
 *
 * Matches the `page_meta_data` fields in the analytics schema.
 */
public struct PageMetaData: Codable {
    /// Canonical page URL
    public var url: String?
    /// Content author
    public var author: String?
    /// Page/screen title
    public var title: String?
    /// Featured image URL
    public var thumbnailUrl: String?
    /// Content section/category
    public var articleSection: String?
    /// Comma-separated keywords
    public var keywords: String?
    /// Content creation date (ISO 8601)
    public var dateCreated: String?
    /// Content last modified date (ISO 8601)
    public var dateModified: String?
    /// Content publication date (ISO 8601)
    public var datePublished: String?

    public init(
        url: String? = nil,
        author: String? = nil,
        title: String? = nil,
        thumbnailUrl: String? = nil,
        articleSection: String? = nil,
        keywords: String? = nil,
        dateCreated: String? = nil,
        dateModified: String? = nil,
        datePublished: String? = nil
    ) {
        self.url = url
        self.author = author
        self.title = title
        self.thumbnailUrl = thumbnailUrl
        self.articleSection = articleSection
        self.keywords = keywords
        self.dateCreated = dateCreated
        self.dateModified = dateModified
        self.datePublished = datePublished
    }

    enum CodingKeys: String, CodingKey {
        case url
        case author
        case title
        case thumbnailUrl = "thumbnailUrl"
        case articleSection = "articleSection"
        case keywords
        case dateCreated = "dateCreated"
        case dateModified = "dateModified"
        case datePublished = "datePublished"
    }
}

// MARK: - Enhanced Analytics Events (matching web SDK data model)

/**
 * Screen/Page view event - matches `/pageviews` endpoint
 *
 * Sent when a new screen is viewed. Contains session context and device info.
 */
internal struct PageViewEvent: Codable {
    // Web schema common fields
    let payloadVersion: String  // Must be semver format "x.y.z"
    let configVersion: Int
    let browserTimestamp: String  // ISO 8601 with milliseconds

    // Session/user context
    let sessionId: String
    let userId: String
    let propertyId: String
    let newUser: Bool

    // Page context
    let pageId: String
    let sessionDepth: Int
    let pageUrl: String  // Must be valid URL or empty string (mobile uses empty)
    let pageSearch: String  // Query string, empty for mobile
    let pageReferrer: String  // Must be valid URL or empty string

    // Device context (flattened)
    let browser: String
    let device: String
    let os: String
    let country: String
    let region: String  // Must be 2 chars or empty

    // Attribution
    let sessionSource: String
    let sessionMedium: String
    let utmSource: String
    let utmMedium: String
    let utmCampaign: String
    let utmTerm: String
    let utmContent: String

    // Click IDs for attribution
    let gclid: String
    let fbclid: String

    // Additional required fields
    let acctType: String  // "anonymous" for mobile
    let diiSource: String  // empty for mobile
    let gamNetworkCode: String
    let amznPubId: String  // Must be valid UUID (nil UUID for empty)
    let customDimensions: [String: String]

    // Page metadata (only for pageview events)
    let pageMetaData: PageMetaData?

    enum CodingKeys: String, CodingKey {
        case payloadVersion = "payload_version"
        case configVersion = "config_version"
        case browserTimestamp = "browser_timestamp"
        case sessionId = "session_id"
        case userId = "user_id"
        case propertyId = "property_id"
        case newUser = "new_user"
        case pageId = "page_id"
        case sessionDepth = "session_depth"
        case pageUrl = "page_url"
        case pageSearch = "page_search"
        case pageReferrer = "page_referrer"
        case browser
        case device
        case os
        case country
        case region
        case sessionSource = "session_source"
        case sessionMedium = "session_medium"
        case utmSource = "utm_source"
        case utmMedium = "utm_medium"
        case utmCampaign = "utm_campaign"
        case utmTerm = "utm_term"
        case utmContent = "utm_content"
        case gclid
        case fbclid
        case acctType = "acct_type"
        case diiSource = "dii_source"
        case gamNetworkCode = "gam_network_code"
        case amznPubId = "amzn_pub_id"
        case customDimensions = "custom_dimensions"
        case pageMetaData = "page_meta_data"
    }
}

/**
 * Individual impression record - nested inside ImpressionBatchEvent
 *
 * Represents a single ad impression with auction/bid data.
 */
internal struct ImpressionRecord: Codable {
    /// Placement identifier (slot_id in web SDK)
    let slotId: String

    /// GAM ad unit path
    let gamUnit: String

    /// GAM price bucket
    let gamPriceBucket: String

    /// Unique identifier for this impression
    let impressionId: String

    /// Auction ID (from Prebid) - REQUIRED, must be valid UUID
    let auctionId: String

    /// Refresh count for this placement
    let refreshCount: Int

    /// Winning bidder name (mutable: ILRD precision may reattribute S2S → google_ad_exchange)
    var adBidder: String

    /// Ad size (e.g., "320x50")
    let adSize: String

    /// Bid price/revenue in CPM
    var adPrice: Double

    /// Floor price
    let adFloorPrice: Double

    /// Minimum bid to win
    let minBidToWin: Double

    /// Advertiser ID from GAM
    let advertiserId: String

    /// Campaign ID from GAM
    let campaignId: String

    /// Line item ID from GAM
    let lineItemId: String

    /// Creative ID from GAM response
    let creativeId: String

    /// Amazon bid data (if applicable)
    let adAmznbid: String

    /// Amazon price data (if applicable)
    let adAmznp: String

    /// Demand type (banner, video, etc.)
    let adDemandType: String

    /// Demand channel (e.g., "Prebid Header", "GAM Direct").
    /// Mutable: ILRD precision may reattribute S2S → "Google Ad Exchange".
    var demandChannel: String

    /// Custom dimensions
    let customDimensions: [String: String]

    enum CodingKeys: String, CodingKey {
        case slotId = "slot_id"
        case gamUnit = "gam_unit"
        case gamPriceBucket = "gam_price_bucket"
        case impressionId = "impression_id"
        case auctionId = "auction_id"
        case refreshCount = "refresh_count"
        case adBidder = "ad_bidder"
        case adSize = "ad_size"
        case adPrice = "ad_price"
        case adFloorPrice = "ad_floor_price"
        case minBidToWin = "min_bid_to_win"
        case advertiserId = "advertiser_id"
        case campaignId = "campaign_id"
        case lineItemId = "line_item_id"
        case creativeId = "creative_id"
        case adAmznbid = "ad_amznbid"
        case adAmznp = "ad_amznp"
        case adDemandType = "ad_demand_type"
        case demandChannel = "demand_channel"
        case customDimensions = "custom_dimensions"
    }
}

/**
 * Impression batch event - matches `/impressions` endpoint
 *
 * Contains session/page context with nested array of impression records.
 * This matches the web SDK format where impressions are batched with pageview context.
 */
internal struct ImpressionBatchEvent: Codable {
    // Web schema common fields
    let payloadVersion: String  // Must be semver format "x.y.z"
    let configVersion: Int
    let browserTimestamp: String  // ISO 8601 with milliseconds

    // Session/user context
    let sessionId: String
    let userId: String
    let propertyId: String
    let newUser: Bool
    let pageId: String
    let sessionDepth: Int

    // Page context
    let pageUrl: String  // Must be valid URL or empty string
    let pageSearch: String
    let pageReferrer: String

    // Device context (flattened)
    let browser: String
    let device: String
    let os: String
    let country: String
    let region: String  // Must be 2 chars or empty

    // Attribution
    let sessionSource: String
    let sessionMedium: String
    let utmSource: String
    let utmMedium: String
    let utmCampaign: String
    let utmTerm: String
    let utmContent: String

    // Click IDs for attribution
    let gclid: String
    let fbclid: String

    // Additional required fields
    let acctType: String
    let diiSource: String
    let gamNetworkCode: String
    let amznPubId: String  // Must be valid UUID (nil UUID for empty)
    let customDimensions: [String: String]

    // Nested impressions array
    var impressions: [ImpressionRecord]

    enum CodingKeys: String, CodingKey {
        case payloadVersion = "payload_version"
        case configVersion = "config_version"
        case browserTimestamp = "browser_timestamp"
        case sessionId = "session_id"
        case userId = "user_id"
        case propertyId = "property_id"
        case newUser = "new_user"
        case pageId = "page_id"
        case sessionDepth = "session_depth"
        case pageUrl = "page_url"
        case pageSearch = "page_search"
        case pageReferrer = "page_referrer"
        case browser
        case device
        case os
        case country
        case region
        case sessionSource = "session_source"
        case sessionMedium = "session_medium"
        case utmSource = "utm_source"
        case utmMedium = "utm_medium"
        case utmCampaign = "utm_campaign"
        case utmTerm = "utm_term"
        case utmContent = "utm_content"
        case gclid
        case fbclid
        case acctType = "acct_type"
        case diiSource = "dii_source"
        case gamNetworkCode = "gam_network_code"
        case amznPubId = "amzn_pub_id"
        case customDimensions = "custom_dimensions"
        case impressions
    }
}

/**
 * Click data object nested inside ClickEvent
 *
 * Contains the click-specific fields per backend schema.
 */
internal struct ClickData: Codable {
    /// Unique click identifier
    let clickId: String

    /// Placement identifier (slot_id in web SDK)
    let slotId: String

    /// Reference to the impression that was clicked - must be valid UUID
    let impressionId: String

    /// Refresh count for this placement
    let refreshCount: Int

    /// Amazon price data (if applicable)
    let adAmznp: String

    /// Winning bidder name
    let adBidder: String

    /// Ad size (e.g., "320x50")
    let adSize: String

    /// Advertiser ID from GAM
    let advertiserId: String

    /// Campaign ID from GAM
    let campaignId: String

    /// Line item ID from GAM
    let lineItemId: String

    /// Creative ID from GAM response
    let creativeId: String

    /// Demand type (banner, video, etc.)
    let adDemandType: String

    /// Demand channel (e.g., "Prebid Header", "GAM Direct")
    let demandChannel: String

    /// Custom dimensions - values must be string arrays per backend schema
    let customDimensions: [String: [String]]

    enum CodingKeys: String, CodingKey {
        case clickId = "click_id"
        case slotId = "slot_id"
        case impressionId = "impression_id"
        case refreshCount = "refresh_count"
        case adAmznp = "ad_amznp"
        case adBidder = "ad_bidder"
        case adSize = "ad_size"
        case advertiserId = "advertiser_id"
        case campaignId = "campaign_id"
        case lineItemId = "line_item_id"
        case creativeId = "creative_id"
        case adDemandType = "ad_demand_type"
        case demandChannel = "demand_channel"
        case customDimensions = "custom_dimensions"
    }
}

/**
 * Ad click event - matches `/clicks` endpoint
 *
 * Sent when a user clicks on an ad.
 */
internal struct ClickEvent: Codable {
    // Web schema common fields
    let payloadVersion: String  // Must be semver format "x.y.z"
    let configVersion: Int
    let browserTimestamp: String  // ISO 8601 with milliseconds

    // Session/user context
    let sessionId: String
    let userId: String
    let propertyId: String
    let newUser: Bool
    let pageId: String
    let sessionDepth: Int

    // Page context
    let pageUrl: String
    let pageSearch: String
    let pageReferrer: String

    // Device context (flattened)
    let browser: String
    let device: String
    let os: String
    let country: String
    let region: String  // Must be 2 chars or empty

    // Attribution
    let sessionSource: String
    let sessionMedium: String
    let utmSource: String
    let utmMedium: String
    let utmCampaign: String
    let utmTerm: String
    let utmContent: String

    // Click IDs
    let gclid: String
    let fbclid: String

    // Additional required fields
    let acctType: String
    let diiSource: String
    let gamNetworkCode: String
    let amznPubId: String
    let customDimensions: [String: String]

    // Click-specific fields (nested click object per schema)
    let click: ClickData

    enum CodingKeys: String, CodingKey {
        case payloadVersion = "payload_version"
        case configVersion = "config_version"
        case browserTimestamp = "browser_timestamp"
        case sessionId = "session_id"
        case userId = "user_id"
        case propertyId = "property_id"
        case newUser = "new_user"
        case pageId = "page_id"
        case sessionDepth = "session_depth"
        case pageUrl = "page_url"
        case pageSearch = "page_search"
        case pageReferrer = "page_referrer"
        case browser
        case device
        case os
        case country
        case region
        case sessionSource = "session_source"
        case sessionMedium = "session_medium"
        case utmSource = "utm_source"
        case utmMedium = "utm_medium"
        case utmCampaign = "utm_campaign"
        case utmTerm = "utm_term"
        case utmContent = "utm_content"
        case gclid
        case fbclid
        case acctType = "acct_type"
        case diiSource = "dii_source"
        case gamNetworkCode = "gam_network_code"
        case amznPubId = "amzn_pub_id"
        case customDimensions = "custom_dimensions"
        case click
    }
}

/**
 * Viewability data object nested inside ViewabilityEvent
 *
 * Contains the per-slot viewability metrics.
 */
internal struct ViewabilityData: Codable {
    /// Placement identifier (slot_id in web SDK)
    let slotId: String

    /// Reference to the impression - must be valid UUID
    let impressionId: String

    /// Refresh count for this placement
    let refreshCount: Int

    /// Amazon price data (if applicable)
    let adAmznp: String

    /// Winning bidder name
    let adBidder: String

    /// Ad size (e.g., "320x50")
    let adSize: String

    /// Advertiser ID from GAM
    let advertiserId: String

    /// Campaign ID from GAM
    let campaignId: String

    /// Line item ID from GAM
    let lineItemId: String

    /// Creative ID from GAM response
    let creativeId: String

    /// Demand type (banner, video, etc.)
    let adDemandType: String

    /// Demand channel (e.g., "Prebid Header", "GAM Direct")
    let demandChannel: String

    /// Custom dimensions
    let customDimensions: [String: [String]]

    enum CodingKeys: String, CodingKey {
        case slotId = "slot_id"
        case impressionId = "impression_id"
        case refreshCount = "refresh_count"
        case adAmznp = "ad_amznp"
        case adBidder = "ad_bidder"
        case adSize = "ad_size"
        case advertiserId = "advertiser_id"
        case campaignId = "campaign_id"
        case lineItemId = "line_item_id"
        case creativeId = "creative_id"
        case adDemandType = "ad_demand_type"
        case demandChannel = "demand_channel"
        case customDimensions = "custom_dimensions"
    }
}

/**
 * Viewability event - matches `/viewability` endpoint
 *
 * Sent when an ad meets viewability thresholds (e.g., 50% visible for 1 second).
 * Contains session/page context with nested viewability array.
 */
internal struct ViewabilityEvent: Codable {
    // Web schema common fields
    let payloadVersion: String  // Must be semver format "x.y.z"
    let configVersion: Int
    let browserTimestamp: String  // ISO 8601 with milliseconds

    // Session/user context
    let sessionId: String
    let userId: String
    let propertyId: String
    let newUser: Bool
    let pageId: String
    let sessionDepth: Int

    // Page context
    let pageUrl: String
    let pageSearch: String
    let pageReferrer: String

    // Device context (flattened)
    let browser: String
    let device: String
    let os: String
    let country: String
    let region: String  // Must be 2 chars or empty

    // Attribution
    let sessionSource: String
    let sessionMedium: String
    let utmSource: String
    let utmMedium: String
    let utmCampaign: String
    let utmTerm: String
    let utmContent: String

    // Click IDs
    let gclid: String
    let fbclid: String

    // Additional required fields
    let acctType: String
    let diiSource: String
    let gamNetworkCode: String
    let amznPubId: String

    // Nested viewability array (per backend schema)
    var viewability: [ViewabilityData]

    enum CodingKeys: String, CodingKey {
        case payloadVersion = "payload_version"
        case configVersion = "config_version"
        case browserTimestamp = "browser_timestamp"
        case sessionId = "session_id"
        case userId = "user_id"
        case propertyId = "property_id"
        case newUser = "new_user"
        case pageId = "page_id"
        case sessionDepth = "session_depth"
        case pageUrl = "page_url"
        case pageSearch = "page_search"
        case pageReferrer = "page_referrer"
        case browser
        case device
        case os
        case country
        case region
        case sessionSource = "session_source"
        case sessionMedium = "session_medium"
        case utmSource = "utm_source"
        case utmMedium = "utm_medium"
        case utmCampaign = "utm_campaign"
        case utmTerm = "utm_term"
        case utmContent = "utm_content"
        case gclid
        case fbclid
        case acctType = "acct_type"
        case diiSource = "dii_source"
        case gamNetworkCode = "gam_network_code"
        case amznPubId = "amzn_pub_id"
        case viewability
    }
}

/**
 * Engagement event - matches `/engagement` endpoint
 *
 * Tracks user engagement with content/screens.
 */
internal struct EngagementEvent: Codable {
    // Web schema common fields
    let payloadVersion: String  // Must be semver format "x.y.z"
    let configVersion: Int
    let browserTimestamp: String  // ISO 8601 with milliseconds

    // Session/user context
    let sessionId: String
    let userId: String
    let propertyId: String
    let newUser: Bool
    let pageId: String
    let sessionDepth: Int

    // Page context
    let pageUrl: String
    let pageSearch: String
    let pageReferrer: String

    // Device context (flattened)
    let browser: String
    let device: String
    let os: String
    let country: String
    let region: String  // Must be 2 chars or empty

    // Attribution
    let sessionSource: String
    let sessionMedium: String
    let utmSource: String
    let utmMedium: String
    let utmCampaign: String
    let utmTerm: String
    let utmContent: String

    // Click IDs
    let gclid: String
    let fbclid: String

    // Additional required fields
    let acctType: String
    let diiSource: String
    let gamNetworkCode: String
    let amznPubId: String

    /// Custom dimensions - values must be string arrays per backend schema
    let customDimensions: [String: [String]]

    // Engagement-specific fields
    /// Time actively engaged in seconds
    let engagedTime: Int

    /// Total time on page/screen in seconds
    let timeOnPage: Int

    /// Maximum scroll depth percentage (0-100)
    let scrollDepth: Int

    enum CodingKeys: String, CodingKey {
        case payloadVersion = "payload_version"
        case configVersion = "config_version"
        case browserTimestamp = "browser_timestamp"
        case sessionId = "session_id"
        case userId = "user_id"
        case propertyId = "property_id"
        case newUser = "new_user"
        case pageId = "page_id"
        case sessionDepth = "session_depth"
        case pageUrl = "page_url"
        case pageSearch = "page_search"
        case pageReferrer = "page_referrer"
        case browser
        case device
        case os
        case country
        case region
        case sessionSource = "session_source"
        case sessionMedium = "session_medium"
        case utmSource = "utm_source"
        case utmMedium = "utm_medium"
        case utmCampaign = "utm_campaign"
        case utmTerm = "utm_term"
        case utmContent = "utm_content"
        case gclid
        case fbclid
        case acctType = "acct_type"
        case diiSource = "dii_source"
        case gamNetworkCode = "gam_network_code"
        case amznPubId = "amzn_pub_id"
        case customDimensions = "custom_dimensions"
        case engagedTime = "engaged_time"
        case timeOnPage = "time_on_page"
        case scrollDepth = "scroll_depth"
    }
}

// MARK: - Auction Data

/**
 * Auction data from Prebid response
 *
 * Captures bidding information for analytics.
 */
internal struct AuctionData {
    /// Prebid auction ID
    let auctionId: String?

    /// Winning bidder name
    let bidder: String?

    /// Winning bid price in CPM
    let bidPriceCpm: Double?

    /// Creative ID from winning bid
    let creativeId: String?

    /// GAM price bucket (hb_pb value from targeting KVPs)
    let gamPriceBucket: String?

    /// Floor price sent to bidders
    let floorPrice: Double?

    /// Second-highest bid price (for computing min_bid_to_win)
    let secondHighestBid: Double?

    /// Demand channel ("S2S", "Google Ad Exchange", etc.)
    let demandChannel: String?

    /// Empty auction data
    static let empty = AuctionData(
        auctionId: nil, bidder: nil, bidPriceCpm: nil, creativeId: nil,
        gamPriceBucket: nil, floorPrice: nil, secondHighestBid: nil, demandChannel: nil
    )
}

// MARK: - Impression Context

/**
 * Context for tracking an ad impression
 *
 * Aggregates all data needed to track an impression through its lifecycle.
 */
internal struct ImpressionContext {
    /// Unique impression identifier
    let impressionId: String

    /// Placement configuration
    let placementId: String
    /// UUID for backend reporting (slot_id)
    let slotId: String
    let gamAdUnit: String
    let format: String

    /// Size (for banners)
    let width: Int?
    let height: Int?

    /// Auction data (populated after Prebid response)
    var auctionData: AuctionData

    /// Timestamp when impression was created
    let createdAt: Int64

    init(
        placementId: String,
        slotId: String,
        gamAdUnit: String,
        format: String,
        width: Int? = nil,
        height: Int? = nil
    ) {
        self.impressionId = UUID().uuidString
        self.placementId = placementId
        self.slotId = slotId
        self.gamAdUnit = gamAdUnit
        self.format = format
        self.width = width
        self.height = height
        self.auctionData = .empty
        self.createdAt = Int64(Date().timeIntervalSince1970 * 1000)
    }
}
