import AVFoundation
import Combine
import Foundation

@MainActor
final class HermesRepository: ObservableObject {
    @Published private(set) var currentSession: HermesSessionResponse?
    @Published private(set) var currentRound: HermesRound?
    @Published private(set) var isLoading = true
    @Published private(set) var errorCause: Error?
    @Published private(set) var serverClockOffset: TimeInterval?

    private let apiClient: HermesAPIClient
    private var mockRoundIndex = 0

    init(apiClient: HermesAPIClient) {
        self.apiClient = apiClient
    }

    func reset() {
        currentSession = nil
        currentRound = nil
        isLoading = false
        errorCause = nil
        serverClockOffset = nil
        mockRoundIndex = 0
    }

    func bootstrapMock(localeCode: String, appVersion: String) async {
        isLoading = true
        errorCause = nil
        mockRoundIndex = 0
        currentSession = MockHermesData.session(localeCode: localeCode, appVersion: appVersion)
        currentRound = await MockHermesData.round(index: mockRoundIndex, now: Date())
        isLoading = false
    }

    func refreshMockRound() async throws -> HermesRound {
        isLoading = true
        errorCause = nil
        mockRoundIndex = (mockRoundIndex + 1) % MockHermesData.roundCount
        let round = await MockHermesData.round(index: mockRoundIndex, now: Date())
        currentRound = round
        isLoading = false
        return round
    }

    func bootstrap(_ request: HermesSessionStartRequest) async throws -> HermesSessionResponse {
        isLoading = true
        errorCause = nil

        do {
            await syncClock()

            let session: HermesSessionResponse
            if let existingSession = currentSession {
                session = existingSession
            } else {
                session = try await startSession(request)
            }

            if currentRound == nil {
                currentRound = try await loadRoundFromNetwork()
            }

            isLoading = false
            return session
        } catch {
            errorCause = error
            isLoading = false
            throw error
        }
    }

    func refreshRoundFromNetwork() async throws -> HermesRound {
        isLoading = true
        errorCause = nil

        do {
            await syncClock()

            let round = try await loadRoundFromNetwork()
            currentRound = round
            isLoading = false
            return round
        } catch {
            errorCause = error
            isLoading = false
            throw error
        }
    }

    func startSession(_ request: HermesSessionStartRequest) async throws -> HermesSessionResponse {
        let session = try await apiClient.startSession(request)
        currentSession = session
        return session
    }

    func endSession() async throws -> HermesSessionResponse {
        let session = try await apiClient.endSession()
        currentSession = session
        return session
    }

    func submitBetIntent(_ request: HermesBetIntentRequest) async throws -> HermesBetIntentResponse {
        try await apiClient.submitBetIntent(request)
    }

    func currentOdds(marketID: UUID) async throws -> HermesOddsVersion {
        try await apiClient.currentOdds(marketID: marketID)
    }

    func settlement(eventID: UUID) async throws -> HermesSettlement {
        try await apiClient.settlement(eventID: eventID)
    }

    func experimentConfig() async throws -> HermesExperimentConfig {
        try await apiClient.experimentConfig()
    }

    func localization(localeCode: String) async throws -> HermesLocalizationBundle {
        try await apiClient.localization(localeCode: localeCode)
    }

    func submitAnalyticsBatch(_ payload: HermesAnalyticsBatchRequest) async throws {
        try await apiClient.submitAnalyticsBatch(payload)
    }

    func serverNow() -> Date {
        guard let serverClockOffset else {
            return Date()
        }

        return Date().addingTimeInterval(serverClockOffset)
    }

    private func syncClock() async {
        do {
            let health = try await apiClient.health()
            serverClockOffset = health.serverTime.timeIntervalSince(Date())
        } catch {
            return
        }
    }

    private func loadRoundFromNetwork() async throws -> HermesRound {
        let event = try await apiClient.nextEvent()
        let manifest = try await apiClient.eventManifest(eventID: event.id)
        guard let media = manifest.media.first(where: { $0.mediaType == "hls_main" }) ?? manifest.media.first else {
            throw HermesAPIError.invalidResponse
        }

        let market: HermesMarket
        if let manifestMarket = manifest.markets.first {
            market = manifestMarket
        } else {
            let markets = try await apiClient.markets(eventID: event.id)
            guard let fallbackMarket = markets.first else {
                throw HermesAPIError.invalidResponse
            }
            market = fallbackMarket
        }

        let oddsVersion = try await apiClient.currentOdds(marketID: market.id)
        let settlement = try await apiClient.settlement(eventID: event.id)

        return HermesRound(
            event: event,
            media: media,
            market: market,
            oddsVersion: oddsVersion,
            settlement: settlement
        )
    }
}

enum MockHermesData {
    static let roundCount = 3
    private static let fallbackMediaURL = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8")!

    static func session(localeCode: String, appVersion: String) -> HermesSessionResponse {
        HermesSessionResponse(
            sessionId: UUID(uuidString: "00000000-0000-0000-0000-000000000001") ?? UUID(),
            userId: UUID(uuidString: "00000000-0000-0000-0000-000000000002") ?? UUID(),
            startedAt: Date(),
            endedAt: nil,
            experimentVariant: "modern",
            appVersion: appVersion,
            deviceModel: "Demo Device",
            osVersion: "Demo OS",
            localeCode: localeCode,
            devicePlatform: "ios"
        )
    }

    static func round(index: Int, now: Date) async -> HermesRound {
        let scenarios = [
            await scenario(
                eventID: "10000000-0000-0000-0000-000000000001",
                marketID: "20000000-0000-0000-0000-000000000001",
                yesOutcomeID: "30000000-0000-0000-0000-000000000001",
                noOutcomeID: "30000000-0000-0000-0000-000000000002",
                promptKey: "event.question.score",
                sourceRef: "mock-score",
                winningOutcomeID: "30000000-0000-0000-0000-000000000001",
                now: now
            ),
            await scenario(
                eventID: "10000000-0000-0000-0000-000000000011",
                marketID: "20000000-0000-0000-0000-000000000011",
                yesOutcomeID: "30000000-0000-0000-0000-000000000011",
                noOutcomeID: "30000000-0000-0000-0000-000000000012",
                promptKey: "event.question.save",
                sourceRef: "mock-save",
                winningOutcomeID: "30000000-0000-0000-0000-000000000012",
                now: now
            ),
            await scenario(
                eventID: "10000000-0000-0000-0000-000000000021",
                marketID: "20000000-0000-0000-0000-000000000021",
                yesOutcomeID: "30000000-0000-0000-0000-000000000021",
                noOutcomeID: "30000000-0000-0000-0000-000000000022",
                promptKey: "event.question.convert",
                sourceRef: "mock-convert",
                winningOutcomeID: "30000000-0000-0000-0000-000000000021",
                now: now
            ),
        ]

        return scenarios[index % scenarios.count]
    }

    private static func scenario(
        eventID: String,
        marketID: String,
        yesOutcomeID: String,
        noOutcomeID: String,
        promptKey: String,
        sourceRef: String,
        winningOutcomeID: String,
        now: Date
    ) async -> HermesRound {
        let mediaURL = localMediaURL(named: "example-round.mov") ?? fallbackMediaURL
        let previewDurationMs = await assetDurationMs(url: mediaURL) ?? 15_000
        let revealURL = revealMediaURL()
        let revealDurationMs = await assetDurationMs(url: revealURL) ?? 6_000
        let lockAt = now.addingTimeInterval(Double(previewDurationMs) / 1_000.0)
        let settleAt = lockAt.addingTimeInterval(6)

        let event = HermesEvent(
            id: UUID(uuidString: eventID) ?? UUID(),
            sportType: "football",
            sourceRef: sourceRef,
            titleEn: promptKey,
            titleSv: promptKey,
            status: "active",
            previewStartMs: 0,
            previewEndMs: previewDurationMs,
            revealStartMs: 0,
            revealEndMs: revealDurationMs,
            lockAt: lockAt,
            settleAt: settleAt
        )

        let media = HermesEventMedia(
            id: UUID(),
            eventId: event.id,
            mediaType: "hls_main",
            hlsMasterUrl: mediaURL,
            posterUrl: nil,
            durationMs: previewDurationMs,
            previewStartMs: 0,
            previewEndMs: previewDurationMs,
            revealStartMs: 0,
            revealEndMs: revealDurationMs
        )

        let marketUUID = UUID(uuidString: marketID) ?? UUID()
        let market = HermesMarket(
            id: marketUUID,
            eventId: event.id,
            questionKey: promptKey,
            marketType: promptKey,
            status: "open",
            lockAt: lockAt,
            settlementRuleKey: "demo",
            outcomes: [
                HermesOutcome(
                    id: UUID(uuidString: yesOutcomeID) ?? UUID(),
                    marketId: marketUUID,
                    outcomeCode: "yes",
                    labelKey: "round.yes",
                    sortOrder: 0
                ),
                HermesOutcome(
                    id: UUID(uuidString: noOutcomeID) ?? UUID(),
                    marketId: marketUUID,
                    outcomeCode: "no",
                    labelKey: "round.no",
                    sortOrder: 1
                ),
            ]
        )

        let oddsVersionID = UUID()
        let oddsVersion = HermesOddsVersion(
            id: oddsVersionID,
            marketId: market.id,
            versionNo: 1,
            createdAt: now,
            isCurrent: true,
            odds: [
                HermesOutcomeOdds(id: UUID(), oddsVersionId: oddsVersionID, outcomeId: market.outcomes[0].id, decimalOdds: 1.82, fractionalNum: 91, fractionalDen: 50),
                HermesOutcomeOdds(id: UUID(), oddsVersionId: oddsVersionID, outcomeId: market.outcomes[1].id, decimalOdds: 2.05, fractionalNum: 41, fractionalDen: 20),
            ]
        )

        let settlement = HermesSettlement(
            id: UUID(),
            marketId: market.id,
            settledAt: settleAt,
            winningOutcomeId: UUID(uuidString: winningOutcomeID) ?? market.outcomes[0].id
        )

        return HermesRound(event: event, media: media, market: market, oddsVersion: oddsVersion, settlement: settlement)
    }

    static func revealMediaURL() -> URL {
        localMediaURL(named: "example-reveal.mov") ?? fallbackMediaURL
    }

    private static func localMediaURL(named fileName: String) -> URL? {
        let fileURL = URL(fileURLWithPath: fileName)
        if let bundledURL = Bundle.main.url(forResource: fileURL.deletingPathExtension().lastPathComponent, withExtension: fileURL.pathExtension) {
            return bundledURL
        }

        let sourceFileURL = URL(fileURLWithPath: #filePath)
        let repoRoot = sourceFileURL
            .deletingLastPathComponent()
            .deletingLastPathComponent()
            .deletingLastPathComponent()
            .deletingLastPathComponent()

        let candidate = repoRoot.appendingPathComponent(fileName)
        return FileManager.default.fileExists(atPath: candidate.path) ? candidate : nil
    }

    private static func assetDurationMs(url: URL) async -> Int? {
        let asset = AVURLAsset(url: url)
        guard let duration = try? await asset.load(.duration).seconds else {
            return nil
        }
        guard duration.isFinite, duration > 0 else {
            return nil
        }

        return Int((duration * 1_000.0).rounded())
    }
}
