import Foundation
import AuthenticationServices
import Combine
#if targetEnvironment(simulator)
#else
import FirebaseAuth
import FirebaseFirestore
#endif


protocol AuthRepository {
    var signedInState: CurrentValueSubject<SignInState, Never> { get }
    func signInWithStoredToken() async
    @MainActor func signIn(presentationContextProvider: ASWebAuthenticationPresentationContextProviding)
    func signOut()
    @discardableResult func updateUserAvatar(token: String, image: UIImage) async throws -> Bool
    func requestDeletion(token: String) async throws -> DeleteUserResponse
    func resendDeletionRequest(token: String) async throws -> DeleteUserResponse
    func cancelDeletionRequest(token: String) async throws -> DeleteUserResponse
}

protocol ContentRepository {
    var latestPosts: PassthroughSubject<PostCollection, Never> { get }
    var trendingPosts: PassthroughSubject<PostCollection, Never> { get }
    var conversationPosts: PassthroughSubject<PostCollection, Never> { get }
    var topics: PassthroughSubject<[Topic], Never> { get }
    var searchFeatureTopics: PassthroughSubject<[Topic], Never> { get }
    var events: PassthroughSubject<EventCalendar, Never> { get }
    var policyPages: PassthroughSubject<[PolicyPage], Never> { get }
    func updatePosts() async
    func updateTopics() async
    func updateEvents(dateFrom: Date?, dateTo: Date?) async
    func updatePolicyPages() async
    func fetchPosts(for collection: Collection, pageNumber: Int?) async throws -> PostCollection
    func fetchPostsWithIds(_ postIds: [Int], page: Int?) async throws -> PostCollection
    func fetchPostsForTopic(_ topicId: Int, page: Int?) async throws -> PostCollection
    func searchForPosts(_ searchTerm: String, topics: [TopicRef]) async throws -> PostCollection
    func hydratePost(_ postId: Int) async throws -> Post
    func fetchAuthorDetails(_ authorId: Int, page: Int?) async throws -> AuthorResponse
}

protocol CommentRepository {
    func fetchUserComments(token: String) async throws -> Comments
    @discardableResult func postReplyToComment(token: String, reply: Reply) async throws -> CommentResponse
    @discardableResult func postFeedback(feedback: Feedback) async throws -> Bool
}

public class RiotKit: AuthRepository, ContentRepository, CommentRepository {
    
    var signedInState: CurrentValueSubject<SignInState, Never> = .init(.unknown)
    var latestPosts: PassthroughSubject<PostCollection, Never> = .init()
    var trendingPosts: PassthroughSubject<PostCollection, Never> = .init()
    var conversationPosts: PassthroughSubject<PostCollection, Never> = .init()
    var topics: PassthroughSubject<[Topic], Never> = .init()
    var searchFeatureTopics: PassthroughSubject<[Topic], Never> = .init()
    var events: PassthroughSubject<EventCalendar, Never> = .init()
    var policyPages: PassthroughSubject<[PolicyPage], Never> = .init()
    
    private let v1baseUrl: String = {
        if CommandLine.arguments.contains("-dev") {
            return "https://riotact.dev/wp-json/region/v1/"
        } else {
            return "https://the-riotact.com/wp-json/region/v1/"
        }
    }()
    private let v2baseUrl: String = {
        if CommandLine.arguments.contains("-dev") {
            return "https://riotact.dev/wp-json/wp/v2/"
        } else {
            return "https://the-riotact.com/wp-json/wp/v2/"
        }
    }()
    
    private let decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .riot
        return decoder
    }()
    
    private let encoder: JSONEncoder = {
        let encoder = JSONEncoder()
        return encoder
    }()
    
    private let successStatusCodes = 200...299
            
    private let signInController = SignInController()
    
    private let keychainAccountName = "riotact_acc"
    private let keychainServiceName = "riotact"
    
    static let standard: RiotKit = {
        RiotKit()
    }()
    
    func signInWithStoredToken() async {
        // if there is a refresh token stored in the keychain
        // then use it to get fresh tokens
        do {
            if let storedToken = try retrieveToken() {
                async let response = refreshAuthToken(refreshToken: storedToken)
                do {
                    let awaitedresponse = try await response
                    try handleNewAuthResponse(authResponse: awaitedresponse)
            
                } catch {
                    try deleteToken()
                    signedInState.send(.error)
                }
            } else {
                signedInState.send(.signedOut)
            }
        } catch {
            print("error")
        }
    }
    
    func updatePosts() async {
        do {
            let postsResponse = try await fetchPosts(for: .latest)
            latestPosts.send( postsResponse )
            
            let trendingPostsResponse = try await fetchPosts(for: .trending)
            trendingPosts.send( trendingPostsResponse )
            
            let conversationPostsResponse = try await fetchPosts(for: .conversation)
            conversationPosts.send( conversationPostsResponse )
        } catch {
            // TODO: Catch error
            print(error)
            
        }
    }
    
    func fetchPosts(for collection: Collection, pageNumber: Int? = nil) async throws -> PostCollection {
        guard var url = url(for: collection),
                var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
            throw RiotError.fetchError
        }
        
        if let pageNumber = pageNumber {
            components.query = "page=\(pageNumber)"
            guard let modifiedUrl = components.url else {
                throw RiotError.fetchError
            }
            url = modifiedUrl
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = httpMethod(for: collection)
        if case .postIds(let postIds) = collection {
            request.httpBody = "[\(postIds.compactMap({"\($0)"}).joined(separator: ","))]".data(using: .utf8)
            request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        }
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        return try decoder.decode(PostCollection.self, from: data)
    }
    
    func hydratePost(_ postId: Int) async throws -> Post {
        guard let url = URL(string: "\(v1baseUrl)posts/\(postId)") else { throw RiotError.fetchError }
        
        let request = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        return try decoder.decode(Post.self, from: data)
    }
    
    func searchForPosts(_ searchTerm: String, topics: [TopicRef] = []) async throws -> PostCollection {
        
        let encoded = searchTerm.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? searchTerm
        
        guard let url = URL(string: "\(v1baseUrl)posts/search/\(encoded)"),
        var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
            throw RiotError.fetchError
        }
        
        topics.forEach { topic in
            components.queryItems = [URLQueryItem(name: "category[]", value: topic.name)]
        }
        
        guard let urlFromComponents = components.url else {
            throw RiotError.fetchError
        }
        
        var request = URLRequest(url: urlFromComponents)
        request.httpMethod = "GET"
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        return try decoder.decode(PostCollection.self, from: data)
    }
    
    func fetchPostsWithIds(_ postIds: [Int], page: Int?) async throws -> PostCollection {
        return try await fetchPosts(for: .postIds(postIds: postIds), pageNumber: page)
    }
    
    func fetchPostsForTopic(_ topicId: Int, page: Int? = nil) async throws -> PostCollection {
        return try await fetchPosts(for: .topic(topicId: topicId), pageNumber: page)
    }
    
    func updateTopics() async {
        do {
            let topics = try await fetchTopics(for: .primary)
            self.topics.send( topics )
            
            let searchFeatureTopics = try await fetchTopics(for: .searchFeature)
            self.searchFeatureTopics.send( searchFeatureTopics )
        } catch {
            // TODO: Catch error

        }
    }
    
    private func fetchTopics(for collection: TopicCollection) async throws -> [Topic] {
        guard let url = url(for: collection) else { throw RiotError.fetchError }
        
        let request = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        let topics = try decoder.decode([Topic].self, from: data)
        return topics
    }
    
    func updateEvents(dateFrom: Date?, dateTo: Date?) async {
        do {
            let events = try await fetchEvents(dateFrom: dateFrom, dateTo: dateTo)
            self.events.send( events )
            
        } catch {
            // TODO: Catch error
            print(error)
        }
    }
    
    private func fetchEvents(dateFrom: Date?, dateTo: Date?) async throws -> EventCalendar {
        guard var url = URL(string: "\(v1baseUrl)posts/events"),
              var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
            throw RiotError.fetchError
        }
        
        let from = dateFrom ?? Date()
        let toMaybe = dateTo ?? Calendar.current.date(byAdding: .day, value: 7, to: from)
        
        var query = "date_from=\(DateFormatter.dateRequest.string(from: from))"
        if let to = toMaybe {
            query += "&date_to=\(DateFormatter.dateRequest.string(from: to))"
        }
        components.query = query

        guard let modifiedUrl = components.url else {
            throw RiotError.fetchError
        }
        url = modifiedUrl
        
        let request = URLRequest(url: url)

        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        return try decoder.decode(EventCalendar.self, from: data)
    }
    
    func updatePolicyPages() async {
        do {
            let policyPages = try await fetchPolicyPages()
            self.policyPages.send( policyPages )
            
        } catch {
            // TODO: Catch error
            print(error)
        }
    }
    
    func fetchPolicyPages() async throws -> [PolicyPage] {
        guard let url = URL(string: "\(v1baseUrl)menu/pages") else { throw RiotError.fetchError }
        let request = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        let policyPageResponse = try decoder.decode(PolicyPageResponse.self, from: data)
        return policyPageResponse.pages
    }
    
    func fetchAuthorDetails(_ authorId: Int, page: Int?) async throws -> AuthorResponse {
        guard var url = URL(string: "\(v1baseUrl)posts/author/\(authorId)"),
                var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
            throw RiotError.fetchError
        }
        
        if let pageNumber = page {
            components.query = "page=\(pageNumber)"
            guard let modifiedUrl = components.url else {
                throw RiotError.fetchError
            }
            url = modifiedUrl
        }
        
        let request = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        return try decoder.decode(AuthorResponse.self, from: data)
    }
    
    @MainActor
    func signIn(presentationContextProvider: ASWebAuthenticationPresentationContextProviding) {
        Task {
            do {
                let callbackUrl = try await signInController.presentAuthenticationSession(presentationContextProvider: presentationContextProvider)
                guard let callbackUrl = callbackUrl else {
                    signedInState.send(.error)
                    return
                }
                guard let code = authenticationCode(from: callbackUrl) else {
                    print("no authentication code in url: \(callbackUrl)")
                    signedInState.send(.error)
                    return
                }

                let response = try await fetchAuthToken(code: code)
                try handleNewAuthResponse(authResponse: response)

            } catch {
                try deleteToken()
                signedInState.send(.error)
            }
        }
    }
    
    private func handleNewAuthResponse(authResponse: AuthResponse) throws {
        signedInState.send(.signedIn(response: authResponse))
        try storeToken(token: authResponse)
        
#if targetEnvironment(simulator)
#else
        Task {
            let firebaseAuthResponse = try await Auth.auth().signInAnonymously()
            let user = firebaseAuthResponse.user
            let db = Firestore.firestore()
            let userDocumentRef = db.collection("users").document(user.uid)
            
            let _ = try await userDocumentRef.setData([
                "rid": authResponse.id,
                "rtoken": authResponse.token
            ], merge: true)
        }
#endif
    }
    
    func signOut() {
        do {
            try deleteToken()
            signedInState.send(.signedOut)
        } catch {
            signedInState.send(.error)
        }
    }
    
    func fetchAuthToken(code: String) async throws -> AuthResponse {
        guard let url = URL(string: "\(v1baseUrl)user/exchange") else { throw RiotError.fetchError }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = "{\"token\": \"\(code)\"}".data(using: .utf8)
        request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        let authToken = try decoder.decode(AuthResponse.self, from: data)
        return authToken
    }
    
    func refreshAuthToken(refreshToken: String) async throws -> AuthResponse {
        guard let url = URL(string: "\(v1baseUrl)user/refresh") else { throw RiotError.fetchError }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = "{\"token\": \"\(refreshToken)\"}".data(using: .utf8)
        request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        let authToken = try decoder.decode(AuthResponse.self, from: data)
        return authToken
    }
    
    func fetchUserComments(token: String) async throws -> Comments {
        guard let url = URL(string: "\(v1baseUrl)user/comments") else { throw RiotError.fetchError }
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw RiotError.fetchError }
        let comments = try decoder.decode(Comments.self, from: data)
        return comments
    }
    
    @discardableResult
    func postReplyToComment(token: String, reply: Reply) async throws -> CommentResponse {
        guard let url = URL(string: "\(v2baseUrl)comments") else { throw RiotError.urlError }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        request.httpBody = try encoder.encode(reply)
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        if let dataString = String(data: data, encoding: .utf8){
            print(dataString)
        }
        
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode,
              successStatusCodes.contains(statusCode) else {
            throw RiotError.submitError
        }
        
        return try decoder.decode(CommentResponse.self, from: data)
    }
    
    @discardableResult
    func postFeedback(feedback: Feedback) async throws -> Bool {
        guard let url = URL(string: "\(v1baseUrl)feedback/send") else { throw RiotError.urlError }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        request.httpBody = try encoder.encode(feedback)
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        if let dataString = String(data: data, encoding: .utf8){
            print(dataString)
        }
        
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode,
              successStatusCodes.contains(statusCode) else {
            throw RiotError.submitError
        }
        return true
    }
    
    @discardableResult
    func updateUserAvatar(token: String, image: UIImage) async throws -> Bool {
        guard let url = URL(string: "\(v1baseUrl)user/avatar") else { throw RiotError.urlError }
        guard let imageData = image.pngData() else { throw RiotError.dataError }
        
        var request = URLRequest(url: url)
        request.httpMethod = "PUT"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        request.setValue("image/png", forHTTPHeaderField: "Content-Type")
        
        request.httpBody = imageData
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        if let dataString = String(data: data, encoding: .utf8){
            print(dataString)
        }
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode,
              successStatusCodes.contains(statusCode) else {
            throw RiotError.submitError
        }
        return true
    }
    
    func requestDeletion(token: String) async throws -> DeleteUserResponse {
        guard let url = URL(string: "\(v1baseUrl)user/delete") else { throw RiotError.urlError }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        if let dataString = String(data: data, encoding: .utf8){
            print(dataString)
        }
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode,
              successStatusCodes.contains(statusCode) else {
            throw RiotError.submitError
        }
        
        return try decoder.decode(DeleteUserResponse.self, from: data)
    }
    
    func resendDeletionRequest(token: String) async throws -> DeleteUserResponse {
        guard let url = URL(string: "\(v1baseUrl)user/delete/resend") else { throw RiotError.urlError }
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        if let dataString = String(data: data, encoding: .utf8){
            print(dataString)
        }
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode,
              successStatusCodes.contains(statusCode) else {
            throw RiotError.submitError
        }
        
        return try decoder.decode(DeleteUserResponse.self, from: data)
    }
    
    func cancelDeletionRequest(token: String) async throws -> DeleteUserResponse {
        guard let url = URL(string: "\(v1baseUrl)user/delete/cancel") else { throw RiotError.urlError }
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
        let (data, response) = try await URLSession.shared.data(forRequest: request)
        
        if let dataString = String(data: data, encoding: .utf8){
            print(dataString)
        }
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode,
              successStatusCodes.contains(statusCode) else {
            throw RiotError.submitError
        }
        
        return try decoder.decode(DeleteUserResponse.self, from: data)
    }
    
    private func httpMethod(for collection: Collection) -> String {
        switch collection {
        case .postIds:
            return "POST"
        default:
            return "GET"
        }
    }
    
    private func url(for collection: Collection) -> URL? {
        let url: URL?
        switch collection {
        case .latest:
            url = URL(string: "\(v1baseUrl)posts/latest")
        case .trending:
            url = URL(string: "\(v1baseUrl)posts/trending")
        case .conversation:
            url = URL(string: "\(v1baseUrl)posts/conversations")
        case .topic(topicId: let topicId):
            url = URL(string: "\(v1baseUrl)posts/category/\(topicId)")
        case .postIds:
            url = URL(string: "\(v1baseUrl)posts/bookmarks")
        }
        return url
    }
    
    private func url(for topicCollection: TopicCollection) -> URL? {
        let url: URL?
        switch topicCollection {
        case .primary:
            url = URL(string: "\(v1baseUrl)menu/primary")
        case .searchFeature:
            url = URL(string: "\(v1baseUrl)menu/search")
        }
        return url
    }
    
    private func authenticationCode(from callbackURL: URL) -> String? {
        if let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
           let queryItems = components.queryItems,
            let queryItem = queryItems.first(where: {
                $0.name.caseInsensitiveCompare("code") == .orderedSame
            }) {
            return queryItem.value
        }
        return nil
    }

    private func storeToken(token: AuthResponse) throws {
        guard let refreshTokenData = token.refreshToken.data(using: .utf8) else { throw KeychainError.badData }
        let addQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: keychainAccountName,
            kSecAttrService as String: keychainServiceName,
            kSecValueData as String: refreshTokenData
        ]
        let status = SecItemAdd(addQuery as CFDictionary, nil)
        switch status {
        case errSecSuccess:
            break
        case errSecDuplicateItem:
            // There is already a stored refresh token, so we need to update it
            try updateToken(token: token)
        default:
            throw KeychainError.addError
        }
    }
    
    private func updateToken(token: AuthResponse) throws {
        guard let refreshTokenData = token.refreshToken.data(using: .utf8) else { throw KeychainError.badData }
        let query: [String: Any] = [
          kSecClass as String: kSecClassGenericPassword,
          kSecAttrAccount as String: keychainAccountName,
          kSecAttrService as String: keychainServiceName
        ]
        let attributes: [String: Any] = [
          kSecValueData as String: refreshTokenData
        ]
        let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
        guard status != errSecItemNotFound else {
            throw KeychainError.notFound
        }
        guard status == errSecSuccess else {
            throw KeychainError.updateError
        }
    }
    
    private func retrieveToken() throws -> String? {
        let readQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: keychainAccountName,
            kSecAttrService as String: keychainServiceName,
            kSecMatchLimit as String: kSecMatchLimitOne,
            kSecReturnAttributes as String: true,
            kSecReturnData as String: true
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(readQuery as CFDictionary, &item)
        guard status != errSecItemNotFound else {
            return nil
        }
        guard status == errSecSuccess else {
            throw KeychainError.readError
        }
        guard
            let existingItem = item as? [String: Any],
            let itemData = existingItem[kSecValueData as String] as? Data,
            let itemValue = String(data: itemData, encoding: .utf8) else {
                throw KeychainError.conversionError
            }
        return itemValue
    }
    
    private func deleteToken() throws {
        let query: [String: Any] = [
          kSecClass as String: kSecClassGenericPassword,
          kSecAttrAccount as String: keychainAccountName,
          kSecAttrService as String: keychainServiceName
        ]

        let status = SecItemDelete(query as CFDictionary)
        guard status == errSecSuccess || status == errSecItemNotFound else {
            throw KeychainError.deleteError
        }
    }
    
    enum AuthenticationError: LocalizedError {
        case missingCode

        public var errorDescription: String? {
            switch self {
                case .missingCode:
                    return NSLocalizedString("The authentication callback URL did not contain a code.", comment: "")
            }
        }
    }
    
    enum KeychainError: LocalizedError {
        case badData
        case addError
        case notFound
        case readError
        case conversionError
        case updateError
        case deleteError

        public var errorDescription: String? {
            switch self {
                case .badData:
                    return NSLocalizedString("The refresh token could not be converted to data.", comment: "")
                case .addError:
                    return NSLocalizedString("Unable to add the refresh token to the keychain", comment: "")
                case .notFound:
                    return NSLocalizedString("Unable to find the refresh token in the keychain", comment: "")
                case .readError:
                    return NSLocalizedString("Unable to read the refresh token from the keychain", comment: "")
                case .conversionError:
                    return NSLocalizedString("Unable to convert the refresh token data", comment: "")
                case .updateError:
                    return NSLocalizedString("Unable to update the refresh token", comment: "")
                case .deleteError:
                    return NSLocalizedString("Unable to delete the refresh token", comment: "")
            }
        }

    }
}

class SignInController {
    
    private static let authUrl: String = {
        if CommandLine.arguments.contains("-dev") {
            return "https://riotact.dev/wp-login.php?redirect_to=https%3A%2F%2Friotact.dev%2Flogin%2Fios"
        } else {
            return "https://the-riotact.com/wp-login.php?redirect_to=https%3A%2F%2Fthe-riotact.com%2Flogin%2Fios"
        }
    }()
    
    private let authSession: WebAuthenticationSession = {
       let auth = WebAuthenticationSession(
        url: URL(string: SignInController.authUrl)!,
        callbackURLScheme: "riotact",
        prefersEphemeralWebBrowserSession: false
       )
        return auth
    }()
    
    func presentAuthenticationSession(presentationContextProvider: ASWebAuthenticationPresentationContextProviding) async throws -> URL? {
        try await authSession.start(presentationContextProvider: presentationContextProvider)
    }
    
}

// A structured concurrency wrapper for ASWebAuthenticationSession

private class WebAuthenticationSession {

    private let url: URL
    private let callbackURLScheme: String
    private let prefersEphemeralWebBrowserSession: Bool

    private var session: ASWebAuthenticationSession?

    init(url: URL, callbackURLScheme: String, prefersEphemeralWebBrowserSession: Bool = false) {
        self.url = url
        self.callbackURLScheme = callbackURLScheme
        self.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession
    }

    @MainActor
    func start(presentationContextProvider: ASWebAuthenticationPresentationContextProviding) async throws -> URL? {
        return try await withTaskCancellationHandler {
            session?.cancel()
        } operation: {
            try await withCheckedThrowingContinuation { continuation in
                let authSession = ASWebAuthenticationSession(url: self.url, callbackURLScheme: self.callbackURLScheme) { callbackURL, error in
                    if let callbackURL = callbackURL, error == nil {
                        continuation.resume(returning: callbackURL)
                    } else {
                        if let webAuthenticationError = error as? ASWebAuthenticationSessionError,
                           webAuthenticationError.code == .canceledLogin {
                            continuation.resume(returning: nil)
                        } else {
                            let error = error ?? URLError.badServerResponse
                            continuation.resume(throwing: error)
                        }
                    }
                }
                authSession.presentationContextProvider = presentationContextProvider
                authSession.start()
                self.session = authSession
            }
        }
    }

}

class ShimViewController: UIViewController, ASWebAuthenticationPresentationContextProviding {
  func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
    return ASPresentationAnchor()
  }
}
