//
//  APIService+Block.swift
//  Mastodon
//
//  Created by MainasuK Cirno on 2021-4-2.
//

import UIKit
import Combine
import CoreData
import CoreDataStack
import CommonOSLog
import MastodonSDK

extension APIService {
    
    func toggleBlock(
        for mastodonUser: MastodonUser,
        activeMastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
    ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
        let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
        let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
        
        return blockUpdateLocal(
            mastodonUserObjectID: mastodonUser.objectID,
            mastodonAuthenticationBox: activeMastodonAuthenticationBox
        )
        .receive(on: DispatchQueue.main)
        .handleEvents { _ in
            impactFeedbackGenerator.prepare()
        } receiveOutput: { _ in
            impactFeedbackGenerator.impactOccurred()
        } receiveCompletion: { completion in
            switch completion {
            case .failure(let error):
                // TODO: handle error
                os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] local relationship update fail", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
                assertionFailure(error.localizedDescription)
            case .finished:
                os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] local relationship update success", ((#file as NSString).lastPathComponent), #line, #function)
            break
            }
        }
        .flatMap { blockQueryType, mastodonUserID -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
            return self.blockUpdateRemote(
                blockQueryType: blockQueryType,
                mastodonUserID: mastodonUserID,
                mastodonAuthenticationBox: activeMastodonAuthenticationBox
            )
        }
        .receive(on: DispatchQueue.main)
        .handleEvents(receiveCompletion: { [weak self] completion in
            guard let self = self else { return }
            switch completion {
            case .failure(let error):
                os_log("%{public}s[%{public}ld], %{public}s: [Relationship] remote friendship update fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
                // TODO: handle error

                // rollback

                self.blockUpdateLocal(
                    mastodonUserObjectID: mastodonUser.objectID,
                    mastodonAuthenticationBox: activeMastodonAuthenticationBox
                )
                .sink { completion in
                    os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Friendship] rollback finish", ((#file as NSString).lastPathComponent), #line, #function)
                } receiveValue: { _ in
                    // do nothing
                    notificationFeedbackGenerator.prepare()
                    notificationFeedbackGenerator.notificationOccurred(.error)
                }
                .store(in: &self.disposeBag)

            case .finished:
                notificationFeedbackGenerator.notificationOccurred(.success)
                os_log("%{public}s[%{public}ld], %{public}s: [Friendship] remote friendship update success", ((#file as NSString).lastPathComponent), #line, #function)
            }
        })
        .eraseToAnyPublisher()
    }
    
}

extension APIService {
    
    // update database local and return block query update type for remote request
    func blockUpdateLocal(
        mastodonUserObjectID: NSManagedObjectID,
        mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
    ) -> AnyPublisher<(Mastodon.API.Account.BlockQueryType, MastodonUser.ID), Error> {
        let domain = mastodonAuthenticationBox.domain
        let requestMastodonUserID = mastodonAuthenticationBox.userID
        
        var _targetMastodonUserID: MastodonUser.ID?
        var _queryType: Mastodon.API.Account.BlockQueryType?
        let managedObjectContext = backgroundManagedObjectContext
        
        return managedObjectContext.performChanges {
            let request = MastodonUser.sortedFetchRequest
            request.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
            request.fetchLimit = 1
            request.returnsObjectsAsFaults = false
            guard let _requestMastodonUser = managedObjectContext.safeFetch(request).first else {
                assertionFailure()
                return
            }
            
            let mastodonUser = managedObjectContext.object(with: mastodonUserObjectID) as! MastodonUser
            _targetMastodonUserID = mastodonUser.id
            
            let isBlocking = (mastodonUser.blockingBy ?? Set()).contains(_requestMastodonUser)
            _queryType = isBlocking ? .unblock : .block
            mastodonUser.update(isBlocking: !isBlocking, by: _requestMastodonUser)
        }
        .tryMap { result in
            switch result {
            case .success:
                guard let targetMastodonUserID = _targetMastodonUserID,
                      let queryType = _queryType else {
                    throw APIError.implicit(.badRequest)
                }
                return (queryType, targetMastodonUserID)
                
            case .failure(let error):
                assertionFailure(error.localizedDescription)
                throw error
            }
        }
        .eraseToAnyPublisher()
    }
    
    func blockUpdateRemote(
        blockQueryType: Mastodon.API.Account.BlockQueryType,
        mastodonUserID: MastodonUser.ID,
        mastodonAuthenticationBox: AuthenticationService.MastodonAuthenticationBox
    ) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> {
        let domain = mastodonAuthenticationBox.domain
        let authorization = mastodonAuthenticationBox.userAuthorization
        let requestMastodonUserID = mastodonAuthenticationBox.userID

        return Mastodon.API.Account.block(
            session: session,
            domain: domain,
            accountID: mastodonUserID,
            blockQueryType: blockQueryType,
            authorization: authorization
        )
        .flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Relationship>, Error> in
            let managedObjectContext = self.backgroundManagedObjectContext
            return managedObjectContext.performChanges {
                let requestMastodonUserRequest = MastodonUser.sortedFetchRequest
                requestMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: requestMastodonUserID)
                requestMastodonUserRequest.fetchLimit = 1
                guard let requestMastodonUser = managedObjectContext.safeFetch(requestMastodonUserRequest).first else { return }

                let lookUpMastodonUserRequest = MastodonUser.sortedFetchRequest
                lookUpMastodonUserRequest.predicate = MastodonUser.predicate(domain: domain, id: mastodonUserID)
                lookUpMastodonUserRequest.fetchLimit = 1
                let lookUpMastodonUser = managedObjectContext.safeFetch(lookUpMastodonUserRequest).first

                if let lookUpMastodonUser = lookUpMastodonUser {
                    let entity = response.value
                    APIService.CoreData.update(user: lookUpMastodonUser, entity: entity, requestMastodonUser: requestMastodonUser, domain: domain, networkDate: response.networkDate)
                }
            }
            .tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Relationship> in
                switch result {
                case .success:
                    return response
                case .failure(let error):
                    throw error
                }
            }
            .eraseToAnyPublisher()
        }
        .handleEvents(receiveCompletion: { [weak self] completion in
            guard let _ = self else { return }
            switch completion {
            case .failure(let error):
                // TODO: handle error in banner
                os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: [Relationship] block update fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)

            case .finished:
                // TODO: update relationship
                switch blockQueryType {
                case .block:
                    break
                case .unblock:
                    break
                }
            }
        })
        .eraseToAnyPublisher()
    }
    
}

