// Copyright © 2023 Mastodon gGmbH. All rights reserved.

import Foundation
import CoreDataStack
import MastodonSDK

public struct MastodonAuthentication: Codable, Hashable, UserIdentifier {
    
    public static let fallbackCharactersReservedPerURL = 23

    public enum InstanceConfiguration: Codable, Hashable {
        case v1(Mastodon.Entity.Instance)
        case v2(Mastodon.Entity.V2.Instance, TranslationLanguages)
        
        public func canTranslateFrom(_ sourceLocale: String, to targetLanguage: String) -> Bool {
            switch self {
            case .v1:
                return false
            case let .v2(instance, translationLanguages):
                guard instance.configuration?.translation?.enabled == true else { return false }
                return translationLanguages[sourceLocale]?.contains(targetLanguage) == true
            }
        }
        
        public var instanceConfigLimitingProperties: InstanceConfigLimitingPropertyContaining? {
            switch self {
            case let .v1(instance):
                return instance.configuration
            case let .v2(instance, _):
                return instance.configuration
            }
        }
        
        public var canFollowTags: Bool {
            let version: String?
            switch self {
            case let .v1(instance):
                version = instance.version
            case let .v2(instance, _):
                version = instance.version
            }
            return version?.majorServerVersion(greaterThanOrEquals: 4) ?? false // following Tags is support beginning with Mastodon v4.0.0
        }
        
        public var charactersReservedPerURL: Int {
            switch self {
            case let .v1(instance):
                return instance.configuration?.statuses?.charactersReservedPerURL ?? fallbackCharactersReservedPerURL
            case let .v2(instance, _):
                return instance.configuration?.statuses?.charactersReservedPerURL ?? fallbackCharactersReservedPerURL
            }
        }
    }
    
    public typealias ID = UUID
    
    public private(set) var identifier: ID
    public private(set) var domain: String
    public private(set) var username: String

    public private(set) var appAccessToken: String
    public private(set) var userAccessToken: String
    public private(set) var clientID: String
    public private(set) var clientSecret: String
    
    public private(set) var createdAt: Date
    public private(set) var updatedAt: Date
    public private(set) var activedAt: Date

    public private(set) var userID: String

    public private(set) var instanceConfiguration: InstanceConfiguration?
    public private(set) var accountCreatedAt: Date
    
    public var persistenceIdentifier: String {
        "\(username)@\(domain)"
    }
    
    public static func createFrom(
        domain: String,
        userID: String,
        username: String,
        appAccessToken: String,
        userAccessToken: String,
        clientID: String,
        clientSecret: String,
        accountCreatedAt: Date
    ) -> Self {
        let now = Date()
        return MastodonAuthentication(
            identifier: .init(),
            domain: domain,
            username: username,
            appAccessToken: appAccessToken,
            userAccessToken: userAccessToken,
            clientID: clientID,
            clientSecret: clientSecret,
            createdAt: now,
            updatedAt: now,
            activedAt: now,
            userID: userID,
            instanceConfiguration: nil,
            accountCreatedAt: accountCreatedAt
        )
    }
    
    func copy(
        identifier: ID? = nil,
        domain: String? = nil,
        username: String? = nil,
        appAccessToken: String? = nil,
        userAccessToken: String? = nil,
        clientID: String? = nil,
        clientSecret: String? = nil,
        createdAt: Date? = nil,
        updatedAt: Date? = nil,
        activedAt: Date? = nil,
        userID: String? = nil,
        instanceConfiguration: InstanceConfiguration? = nil
    ) -> Self {
        MastodonAuthentication(
            identifier: identifier ?? self.identifier,
            domain: domain ?? self.domain,
            username: username ?? self.username,
            appAccessToken: appAccessToken ?? self.appAccessToken,
            userAccessToken: userAccessToken ?? self.userAccessToken,
            clientID: clientID ?? self.clientID,
            clientSecret: clientSecret ?? self.clientSecret,
            createdAt: createdAt ?? self.createdAt,
            updatedAt: updatedAt ?? self.updatedAt,
            activedAt: activedAt ?? self.activedAt,
            userID: userID ?? self.userID,
            instanceConfiguration: instanceConfiguration ?? self.instanceConfiguration,
            accountCreatedAt: self.accountCreatedAt
        )
    }

    public func account() -> Mastodon.Entity.Account? {

        let account = FileManager
            .default
            .accounts(for: self.userIdentifier())
            .first(where: { $0.id == userID })

        return account
    }

    public func userIdentifier() -> MastodonUserIdentifier {
        MastodonUserIdentifier(domain: domain, userID: userID)
    }

    @MainActor
    func updating(instanceV1 instance: Mastodon.Entity.Instance) -> Self {
        return copy(instanceConfiguration: .v1(instance))
    }
    
    @MainActor
    func updating(instanceV2 instance: Mastodon.Entity.V2.Instance) -> Self {
        guard
            let instanceConfiguration,
            case let InstanceConfiguration.v2(_, translationLanguages) = instanceConfiguration
        else {
            return copy(instanceConfiguration: .v2(instance, [:]))
        }
        return copy(instanceConfiguration: .v2(instance, translationLanguages))
    }
    
    @MainActor
    func updating(translationLanguages: TranslationLanguages) -> Self {
        switch self.instanceConfiguration {
        case .v1(let instance):
            return copy(instanceConfiguration: .v1(instance))
        case .v2(let instance, _):
            return copy(instanceConfiguration: .v2(instance, translationLanguages))
        case .none:
            return self
        }
    }
    
    func updating(activatedAt: Date) -> Self {
        copy(activedAt: activatedAt)
    }

    var authorization: Mastodon.API.OAuth.Authorization {
        .init(accessToken: userAccessToken)
    }
}
