//
//  SubscriptionService.swift
//  Yattee
//
//  Service for managing subscriptions through the selected provider.
//  Supports local (iCloud) and Invidious account subscriptions.
//

import Foundation

/// Errors that can occur during subscription operations.
enum SubscriptionError: LocalizedError, Sendable {
    case alreadySubscribed
    case notSubscribed
    case providerNotAvailable
    case operationFailed(String)

    var errorDescription: String? {
        switch self {
        case .alreadySubscribed:
            return String(localized: "subscription.error.alreadySubscribed")
        case .notSubscribed:
            return String(localized: "subscription.error.notSubscribed")
        case .providerNotAvailable:
            return String(localized: "subscription.error.providerNotAvailable")
        case .operationFailed(let message):
            return message
        }
    }
}

/// Service that manages subscriptions through the selected subscription provider.
/// The provider is determined by the user's subscription account setting.
@MainActor
@Observable
final class SubscriptionService {
    // MARK: - Dependencies

    private let dataManager: DataManager
    private let settingsManager: SettingsManager
    private let instancesManager: InstancesManager
    private let invidiousCredentialsManager: InvidiousCredentialsManager
    private let pipedCredentialsManager: PipedCredentialsManager
    private let invidiousAPI: InvidiousAPI
    private let pipedAPI: PipedAPI

    // MARK: - Providers

    private var localProvider: LocalSubscriptionProvider?
    private var invidiousProvider: InvidiousSubscriptionProvider?
    private var pipedProvider: PipedSubscriptionProvider?

    // MARK: - State

    /// Whether a subscription operation is in progress.
    private(set) var isLoading = false

    /// The last error that occurred.
    private(set) var lastError: Error?

    // MARK: - Initialization

    init(
        dataManager: DataManager,
        settingsManager: SettingsManager,
        instancesManager: InstancesManager,
        invidiousCredentialsManager: InvidiousCredentialsManager,
        pipedCredentialsManager: PipedCredentialsManager,
        invidiousAPI: InvidiousAPI,
        pipedAPI: PipedAPI
    ) {
        self.dataManager = dataManager
        self.settingsManager = settingsManager
        self.instancesManager = instancesManager
        self.invidiousCredentialsManager = invidiousCredentialsManager
        self.pipedCredentialsManager = pipedCredentialsManager
        self.invidiousAPI = invidiousAPI
        self.pipedAPI = pipedAPI

        // Initialize providers
        self.localProvider = LocalSubscriptionProvider(dataManager: dataManager)
        self.invidiousProvider = InvidiousSubscriptionProvider(
            invidiousAPI: invidiousAPI,
            credentialsManager: invidiousCredentialsManager,
            instancesManager: instancesManager,
            settingsManager: settingsManager
        )
        self.pipedProvider = PipedSubscriptionProvider(
            pipedAPI: pipedAPI,
            credentialsManager: pipedCredentialsManager,
            instancesManager: instancesManager,
            settingsManager: settingsManager
        )
    }

    // MARK: - Current Provider

    /// Returns the current subscription provider based on the user's account setting.
    var currentProvider: (any SubscriptionProvider)? {
        let account = settingsManager.subscriptionAccount

        switch account.type {
        case .local:
            return localProvider
        case .invidious:
            return invidiousProvider
        case .piped:
            return pipedProvider
        }
    }

    /// The current subscription account type.
    var currentAccountType: SubscriptionAccountType {
        settingsManager.subscriptionAccount.type
    }

    /// Subscription count for the active account, when known without a network fetch.
    /// Always available for local accounts; nil for server accounts until their cache is populated.
    var cachedSubscriptionCount: Int? {
        currentProvider?.cachedSubscriptionCount
    }

    // MARK: - Subscribe

    /// Subscribes to a channel using the current provider.
    /// - Parameter channel: The channel to subscribe to.
    /// - Throws: SubscriptionProviderError if the operation fails.
    func subscribe(to channel: Channel) async throws {
        guard let provider = currentProvider else {
            throw SubscriptionError.providerNotAvailable
        }

        isLoading = true
        lastError = nil

        do {
            try await provider.subscribe(to: channel)
            postChangeNotificationForServerAccount()
            LoggingService.shared.info(
                "Subscribed to \(channel.name) via \(provider.accountType)",
                category: .general
            )
        } catch {
            lastError = error
            LoggingService.shared.error(
                "Failed to subscribe to \(channel.name): \(error.localizedDescription)",
                category: .general
            )
            throw error
        }

        isLoading = false
    }

    /// Subscribes to a channel from an Author.
    /// - Parameters:
    ///   - author: The author/channel to subscribe to.
    ///   - source: The content source.
    func subscribe(to author: Author, source: ContentSource) async throws {
        let channel = Channel(
            id: ChannelID(source: source, channelID: author.id),
            name: author.name,
            subscriberCount: author.subscriberCount,
            thumbnailURL: author.thumbnailURL
        )
        try await subscribe(to: channel)
    }

    // MARK: - Unsubscribe

    /// Unsubscribes from a channel using the current provider.
    /// - Parameter channelID: The channel ID to unsubscribe from.
    /// - Throws: SubscriptionProviderError if the operation fails.
    func unsubscribe(from channelID: String) async throws {
        guard let provider = currentProvider else {
            throw SubscriptionError.providerNotAvailable
        }

        isLoading = true
        lastError = nil

        do {
            try await provider.unsubscribe(from: channelID)
            postChangeNotificationForServerAccount()
            LoggingService.shared.info(
                "Unsubscribed from \(channelID) via \(provider.accountType)",
                category: .general
            )
        } catch {
            lastError = error
            LoggingService.shared.error(
                "Failed to unsubscribe from \(channelID): \(error.localizedDescription)",
                category: .general
            )
            throw error
        }

        isLoading = false
    }

    // MARK: - Query

    /// Checks if subscribed to a channel.
    /// - Parameter channelID: The channel ID to check.
    /// - Returns: `true` if subscribed, `false` otherwise.
    func isSubscribed(to channelID: String) async -> Bool {
        guard let provider = currentProvider else {
            return false
        }
        return await provider.isSubscribed(to: channelID)
    }

    /// Fetches all subscriptions from the current provider.
    /// - Returns: Array of subscribed channels.
    func fetchSubscriptions() async throws -> [Channel] {
        guard let provider = currentProvider else {
            throw SubscriptionError.providerNotAvailable
        }

        isLoading = true
        lastError = nil

        do {
            let channels = try await provider.fetchSubscriptions()
            isLoading = false
            return channels
        } catch {
            lastError = error
            isLoading = false
            throw error
        }
    }

    /// Synchronously fetches subscriptions for local provider.
    /// Returns nil if the current provider is not local (requires async fetch).
    func fetchSubscriptionsSync() -> [Channel]? {
        guard currentAccountType == .local else {
            return nil
        }

        return dataManager.subscriptions().map { subscription in
            Channel(
                id: ChannelID(source: subscription.contentSource, channelID: subscription.channelID),
                name: subscription.name,
                description: subscription.channelDescription,
                subscriberCount: subscription.subscriberCount,
                thumbnailURL: subscription.avatarURL,
                bannerURL: subscription.bannerURL,
                isVerified: subscription.isVerified
            )
        }
    }

    /// Refreshes the subscription cache from the current provider.
    func refreshCache() async throws {
        guard let provider = currentProvider else {
            throw SubscriptionError.providerNotAvailable
        }

        isLoading = true
        lastError = nil

        do {
            try await provider.refreshCache()
            isLoading = false
        } catch {
            lastError = error
            isLoading = false
            throw error
        }
    }

    // MARK: - Import

    /// Imports parsed external subscriptions (YouTube CSV / OPML) into the active account.
    /// Local accounts write to SwiftData; server accounts subscribe on the server,
    /// one channel at a time. Channels that are already subscribed are skipped.
    func importSubscriptions(_ channels: [(channelID: String, name: String)]) async -> (imported: Int, skipped: Int) {
        guard currentAccountType != .local else {
            return dataManager.importSubscriptionsFromExternal(channels)
        }

        guard let provider = currentProvider else { return (0, channels.count) }

        // Populate the cache so already-subscribed channels can be skipped
        try? await provider.refreshCache()

        var imported = 0
        var skipped = 0
        for entry in channels {
            if await provider.isSubscribed(to: entry.channelID) {
                skipped += 1
                continue
            }
            do {
                try await provider.subscribe(to: Channel(id: .global(entry.channelID), name: entry.name))
                imported += 1
            } catch {
                skipped += 1
                LoggingService.shared.error(
                    "Failed to import subscription \(entry.channelID): \(error.localizedDescription)",
                    category: .general
                )
            }
        }

        if imported > 0 {
            postChangeNotificationForServerAccount()
        }

        return (imported, skipped)
    }

    // MARK: - Private Helpers

    /// Posts a subscriptions-changed notification for server accounts.
    /// Local accounts already post it from DataManager with a change payload.
    private func postChangeNotificationForServerAccount() {
        guard currentAccountType != .local else { return }
        NotificationCenter.default.post(name: .subscriptionsDidChange, object: nil)
    }
}
