import Foundation
import SwiftUI
import Combine

@MainActor
class AppState: ObservableObject {
    @Published var isAuthenticated: Bool = false
    @Published var credentials: XStreamCredentials?
    @Published var playlist: Playlist?
    @Published var isLoading: Bool = false
    @Published var error: NetworkError?
    @Published var watchHistory: [WatchHistoryEntry] = []
    @Published var recentReleases: [VODItem] = []
    @Published var recentReleasesLoading: Bool = false
    @Published var releaseDateFetchProgress: (current: Int, total: Int)? = nil  // For showing fetch progress
    @Published var tmdbReleaseDates: [String: String] = [:]  // VODItem.id -> "YYYY-MM-DD"

    private let authService = AuthenticationService.shared
    private let playlistService = PlaylistService.shared
    private let watchHistoryService = WatchHistoryService.shared
    private let tmdbService = TMDBService.shared
    private let isDevelopmentModeFlag: Bool

    var isDevelopmentMode: Bool { isDevelopmentModeFlag }

    init(developmentMode: Bool = false) {
        self.isDevelopmentModeFlag = developmentMode

        if !developmentMode {
            Task {
                // Configure TMDB API key first, then authenticate
                await configureTMDBAPIKey()
                await checkAuthentication()
            }
        } else {
            Task {
                await configureTMDBAPIKey()
            }
        }
    }

    private func configureTMDBAPIKey() async {
        // Configure TMDB API key if not already set
        guard await !tmdbService.hasAPIKey() else { return }

        do {
            try await tmdbService.saveAPIKey("f0f1acdde1aceede674cba7e668a430b")
        } catch {
            // Silent failure - recent releases feature will be unavailable
        }
    }

    func checkAuthentication() async {
        isLoading = true
        defer { isLoading = false }

        do {
            if let storedCredentials = try await authService.loadStoredCredentials() {
                credentials = storedCredentials
                isAuthenticated = true
                await loadPlaylist()
                await loadWatchHistory()
            } else {
                isAuthenticated = false
            }
        } catch let caughtError {
            isAuthenticated = false
            self.error = caughtError as? NetworkError ?? .unknown(caughtError)
        }
    }

    func authenticate(password: String) async {
        isLoading = true
        error = nil
        defer { isLoading = false }

        do {
            let fetchedCredentials = try await authService.authenticate(password: password)
            credentials = fetchedCredentials
            isAuthenticated = true
            await loadPlaylist()
        } catch let caughtError {
            isAuthenticated = false
            if let networkError = caughtError as? NetworkError {
                self.error = networkError
            } else {
                self.error = .authenticationFailed
            }
        }
    }

    func loadPlaylist(forceRefresh: Bool = false) async {
        guard let credentials = credentials else {
            error = .invalidCredentials
            return
        }

        error = nil

        // Try cache first (unless forcing refresh)
        if !forceRefresh {
            if let cached = await playlistService.getCachedPlaylist() {
                playlist = cached

                // Compute movie groups before loading recent releases
                computeMovieGroupCache()

                // Load recent releases and TV data in background
                Task {
                    await loadRecentReleases()
                }
                print("📺 Spawning loadTVShowData Task...")
                Task {
                    await loadTVShowData()
                }

                // If cache is stale, refresh in background
                if await playlistService.isCacheStale(cached) {
                    Task {
                        await refreshInBackground(credentials: credentials)
                    }
                }
                return
            }
        }

        // No cache or force refresh - show loading and fetch
        isLoading = true
        defer { isLoading = false }

        do {
            let fetchedPlaylist = try await playlistService.fetchVODPlaylist(
                credentials: credentials,
                forceRefresh: forceRefresh
            )
            playlist = fetchedPlaylist

            // Compute movie groups before loading recent releases
            computeMovieGroupCache()

            // Load recent releases and TV data in background
            Task {
                await loadRecentReleases(forceRefresh: forceRefresh)
            }
            print("📺 Spawning loadTVShowData Task (forceRefresh)...")
            Task {
                await loadTVShowData()
            }
        } catch let caughtError {
            if let networkError = caughtError as? NetworkError {
                self.error = networkError
            } else {
                self.error = .unknown(caughtError)
            }
        }
    }

    private func refreshInBackground(credentials: XStreamCredentials) async {
        do {
            let freshPlaylist = try await playlistService.fetchVODPlaylist(
                credentials: credentials,
                forceRefresh: true
            )
            playlist = freshPlaylist
        } catch {
            // Silent failure - cached data remains valid
        }
    }

    func logout() async {
        isLoading = true
        defer { isLoading = false }

        do {
            try await authService.logout()
            await playlistService.clearCache()
            await watchHistoryService.clearHistory()
            await tmdbService.clearCache()
            await ImageCacheService.shared.clearCache()
            credentials = nil
            playlist = nil
            watchHistory = []
            recentReleases = []
            isAuthenticated = false
            error = nil
        } catch let caughtError {
            self.error = caughtError as? NetworkError ?? .unknown(caughtError)
        }
    }

    func clearError() {
        error = nil
    }

    // MARK: - Watch History

    func loadWatchHistory() async {
        watchHistory = await watchHistoryService.loadHistory()
    }

    func updateWatchHistory(_ entry: WatchHistoryEntry) async {
        await watchHistoryService.saveEntry(entry)
        await loadWatchHistory()
    }

    func clearWatchHistory() async {
        await watchHistoryService.clearHistory()
        watchHistory = []
    }

    func getWatchHistoryEntry(forId id: String) -> WatchHistoryEntry? {
        watchHistory.first { $0.id == id }
    }

    // MARK: - Cached Movie Groups (shared across views)

    /// Cached movie groups to avoid recomputing on every load
    /// Exposed for use by VODLibraryView
    @Published var cachedMovieGroups: [MovieGroup] = []
    @Published var movieGroupLookup: [String: MovieGroup] = [:]  // vodItem.id -> MovieGroup
    @Published var cachedReleaseYears: [String: Int] = [:]  // MovieGroup.id -> releaseYear (pre-computed for sorting)
    private var cachedMovieGroupsPlaylistId: Date?

    // MARK: - Cached TV Shows (shared across views)

    /// Cached TV shows to avoid recomputing on every load
    @Published var cachedTVShows: [TVShow] = []
    @Published var cachedTVEpisodes: [VODItem] = []
    @Published var episodeLookup: [String: VODItem] = [:]  // O(1) lookup by episode.id
    private var cachedTVShowsPlaylistId: Date?

    // MARK: - TMDB TV Data

    /// Trending shows from TMDB (matched against our library)
    @Published var trendingTVShows: [TVShow] = []

    /// Shows with recently aired episodes (last_air_date within 14 days)
    @Published var recentlyAiredShows: [TVShow] = []

    /// Air date lookup: show name -> last_air_date (YYYY-MM-DD)
    @Published var tvShowAirDates: [String: String] = [:]

    @Published var tvDataLoading: Bool = false

    // MARK: - Recent Releases (TMDB)

    func loadRecentReleases(forceRefresh: Bool = false) async {
        print("🎬 loadRecentReleases called (forceRefresh: \(forceRefresh))")

        guard playlist != nil else {
            print("🎬 No playlist available")
            return
        }

        // Use pre-computed groups from loading sequence
        let groups = cachedMovieGroups
        print("🎬 Using pre-computed movie groups (\(groups.count) groups)")

        guard !groups.isEmpty else {
            recentReleases = []
            print("🎬 No movie groups available")
            return
        }

        recentReleasesLoading = true

        // Groups are already sorted by year (newest first) in groupedByTitle()
        let top50 = Array(groups.prefix(50).map { $0.primaryItem })
        print("🎬 Selected top 50 most recent movies")

        // Step 2: Immediately display with year-based sorting
        recentReleases = VODItem.sortedByReleaseDate(top50, using: tmdbReleaseDates)
        recentReleasesLoading = false

        // Step 3: Fetch TMDB dates in background (uses persistent cache)
        let hasKey = await tmdbService.hasAPIKey()
        if hasKey {
            print("🎬 Fetching TMDB release dates in background...")

            let releaseDates = await tmdbService.fetchReleaseDatesForMovies(top50) { [weak self] current, total in
                Task { @MainActor in
                    self?.releaseDateFetchProgress = (current, total)
                }
            }

            // Update with fetched dates
            tmdbReleaseDates.merge(releaseDates) { _, new in new }
            releaseDateFetchProgress = nil

            // Re-sort with TMDB dates
            recentReleases = VODItem.sortedByReleaseDate(top50, using: tmdbReleaseDates)
            print("🎬 Re-sorted with \(releaseDates.count) TMDB dates")
        } else {
            print("🎬 No TMDB API key - using year-based sorting only")
        }

        print("🎬 Final recent releases: \(recentReleases.count) movies")
    }

    func setTMDBAPIKey(_ key: String) async throws {
        try await tmdbService.saveAPIKey(key)
        await loadRecentReleases(forceRefresh: true)
    }

    // MARK: - Movie Group Cache

    /// Compute movie group cache from playlist
    func computeMovieGroupCache() {
        guard let playlist = playlist else {
            cachedMovieGroups = []
            movieGroupLookup = [:]
            cachedReleaseYears = [:]
            return
        }

        // Check if already cached for this playlist
        if cachedMovieGroupsPlaylistId == playlist.lastUpdated && !cachedMovieGroups.isEmpty {
            return
        }

        print("🎬 Computing movie group cache...")

        let movies = playlist.vodItems.filter { $0.isMovie }
        let groups = movies.groupedByTitle()

        // Build lookup table
        var lookup: [String: MovieGroup] = [:]
        lookup.reserveCapacity(movies.count)
        for group in groups {
            for version in group.versions {
                lookup[version.id] = group
            }
        }

        // Pre-compute release years for fast sorting
        var releaseYears: [String: Int] = [:]
        releaseYears.reserveCapacity(groups.count)
        for group in groups {
            releaseYears[group.id] = group.primaryItem.releaseYear ?? 0
        }

        cachedMovieGroups = groups
        movieGroupLookup = lookup
        cachedReleaseYears = releaseYears
        cachedMovieGroupsPlaylistId = playlist.lastUpdated

        print("🎬 Cached \(groups.count) movie groups from \(movies.count) movies")
    }

    // MARK: - TV Show Cache

    /// Compute TV show cache from playlist
    func computeTVShowCache() {
        guard let playlist = playlist else {
            cachedTVShows = []
            cachedTVEpisodes = []
            episodeLookup = [:]
            return
        }

        // Check if already cached for this playlist
        if cachedTVShowsPlaylistId == playlist.lastUpdated && !cachedTVShows.isEmpty {
            return
        }

        print("📺 Computing TV show cache...")

        // Get all TV show episodes
        let episodes = playlist.vodItems.filter { $0.isTVShow }
        cachedTVEpisodes = episodes

        // Build episode lookup for O(1) access
        episodeLookup = Dictionary(uniqueKeysWithValues: episodes.map { ($0.id, $0) })

        // Group episodes by show name
        var showDict: [String: [VODItem]] = [:]
        for episode in episodes {
            let showName = TVShow.extractShowName(from: episode.name)
            showDict[showName, default: []].append(episode)
        }

        // Convert to TVShow objects and sort alphabetically
        cachedTVShows = showDict.map { showName, eps in
            TVShow(name: showName, episodes: eps)
        }.sorted { $0.name < $1.name }

        cachedTVShowsPlaylistId = playlist.lastUpdated
        print("📺 Cached \(cachedTVShows.count) TV shows with \(episodes.count) episodes")
    }

    // MARK: - TV Show Data Loading (TMDB Integration)

    func loadTVShowData() async {
        print("📺 loadTVShowData() starting...")
        tvDataLoading = true
        defer {
            tvDataLoading = false
            print("📺 loadTVShowData() completed")
        }

        // 1. Compute TV show cache from playlist
        computeTVShowCache()

        guard !cachedTVShows.isEmpty else {
            print("📺 No TV shows to process")
            return
        }

        print("📺 Processing \(cachedTVShows.count) shows for TMDB matching...")

        // 2. Fetch trending from TMDB and match against our library
        do {
            print("📺 Fetching trending TV shows from TMDB...")
            let trending = try await tmdbService.fetchTrendingTVShows()
            print("📺 Got \(trending.count) trending shows from TMDB")

            // Debug: Print some trending show names
            if !trending.isEmpty {
                let sampleNames = trending.prefix(5).map { $0.name }
                print("📺 Sample trending shows: \(sampleNames)")
            }

            trendingTVShows = matchTVShows(cachedTVShows, against: trending)
            print("📺 Matched \(trendingTVShows.count) trending shows from \(trending.count) TMDB results")

            // Debug: If no matches, print some local show names for comparison
            if trendingTVShows.isEmpty && !trending.isEmpty {
                let sampleLocal = cachedTVShows.prefix(5).map { $0.name }
                print("📺 Sample local shows: \(sampleLocal)")
            }
        } catch {
            print("📺 Failed to fetch trending TV shows: \(error)")
        }

        // 3. Fetch air dates for a subset of shows (limit to avoid rate limiting)
        // Only fetch for first 100 shows to start, can expand later
        let showsToFetch = Array(cachedTVShows.prefix(100))
        print("📺 Fetching air dates for \(showsToFetch.count) shows...")

        let airDates = await tmdbService.fetchAirDatesForShows(showsToFetch)
        tvShowAirDates = airDates
        print("📺 Got air dates for \(airDates.count) shows")

        recentlyAiredShows = filterRecentlyAired(cachedTVShows, using: airDates)
        print("📺 Found \(recentlyAiredShows.count) shows with recently aired episodes")
    }

    /// Match local TV shows against TMDB trending list
    private func matchTVShows(_ localShows: [TVShow], against tmdbShows: [TMDBTVShow]) -> [TVShow] {
        // Build a lookup from normalized title to TMDB show
        var tmdbLookup: [String: TMDBTVShow] = [:]
        for show in tmdbShows {
            let normalized = normalizeShowTitle(show.name)
            tmdbLookup[normalized] = show
        }

        // Match local shows against TMDB
        var matchedShows: [TVShow] = []
        for localShow in localShows {
            let normalized = normalizeShowTitle(localShow.name)
            if tmdbLookup[normalized] != nil {
                matchedShows.append(localShow)
            }
        }

        return matchedShows
    }

    /// Filter shows with recently aired episodes (within last 14 days)
    private func filterRecentlyAired(_ shows: [TVShow], using airDates: [String: String]) -> [TVShow] {
        let calendar = Calendar.current
        let today = Date()
        let cutoffDate = calendar.date(byAdding: .day, value: -Constants.TMDB.recentAirDateDays, to: today) ?? today

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"

        print("📺 Filtering for recently aired (cutoff: \(dateFormatter.string(from: cutoffDate)) to \(dateFormatter.string(from: today)))")

        var recentShows: [TVShow] = []
        for show in shows {
            guard let dateString = airDates[show.name] else {
                continue
            }
            guard let airDate = dateFormatter.date(from: dateString) else {
                print("📺   '\(show.name)' - invalid date format: \(dateString)")
                continue
            }
            if airDate >= cutoffDate && airDate <= today {
                print("📺   ✓ '\(show.name)' - \(dateString) (within window)")
                recentShows.append(show)
            } else {
                print("📺   ✗ '\(show.name)' - \(dateString) (outside window)")
            }
        }

        return recentShows
    }

    /// Normalize show title for matching
    private func normalizeShowTitle(_ title: String) -> String {
        var normalized = title.lowercased()

        // Remove common articles at the beginning
        for article in ["the ", "a ", "an "] {
            if normalized.hasPrefix(article) {
                normalized = String(normalized.dropFirst(article.count))
            }
        }

        // Remove punctuation except spaces
        normalized = normalized.components(separatedBy: CharacterSet.alphanumerics.union(.whitespaces).inverted).joined()

        // Collapse multiple spaces and trim
        normalized = normalized.components(separatedBy: .whitespaces)
            .filter { !$0.isEmpty }
            .joined(separator: " ")
            .trimmingCharacters(in: .whitespaces)

        return normalized
    }
}
