import APIClient
import ComposableArchitecture
import DeeplinkIntents
import Foundation
import NavigationRouterClient
import OmniPlayerClient
import StatsigClient
import Utilities
import Localization

public extension RootCoordinatorV1 {
    func handleDeeplinkAction(_ action: Action.Deeplink, state: inout State) -> Effect<Action> {
        switch action {
        case .clipResponse(let result):
            return openClipFromResponse(result, state: &state)
            
        case .hookResponse(let urlString, let result):
            return openHookFromResponse(urlString, result, state: &state)
            
        case .playlistResponse(let result):
            return openPlaylistFromResponse(result, state: &state)
            
        case .contentShortlinkResponse(let result):
            return openContentFromShortlinkResponse(result, state: &state)
            
        case .remixClipResponse(let result, let type, let style, let lyrics):
            return openRemixClipFromResponse(result, type: type, style: style, lyrics: lyrics, state: &state)
        }
    }
    
    @discardableResult
    func handleDeeplink(_ deeplinkIntent: DeeplinkIntent, state: inout State, source: ContextSource? = nil) -> Effect<Action> {
        if case .blendedCreate = state.destination {
            state.destination = nil
        }
        
        if let source = source {
            omniplayerClient.setContext(SessionContext(source: source))
        }
        
        switch deeplinkIntent {
        case .song(let songId, _):
            return .run { send in
                await send(.deeplink(.clipResponse(Result(catching: {
                    try await api.getClip(songId)
                }))))
            }

        case .hook(let hookId):
            guard FeatureFlag.hooks.isFeedEnabled,
                  FeatureFlag.hooks.hooksDeeplinkingEnabled
            else {
                return .none
            }
            return .run { send in
                // Temporary `urlString` param until we determine when to pass `sourceUrl`
                // and connect playback events to attribution links
                let urlString = "suno://hook/\(hookId)"
                await send(.deeplink(.hookResponse(urlString: urlString, Result(catching: {
                    try await api.getHookById(hookId)
                }))))
            }

        case .profileHook(_, let hookId):
            guard FeatureFlag.hooks.isFeedEnabled,
                  FeatureFlag.hooks.hooksDeeplinkingEnabled
            else {
                return .none
            }
            return .run { send in
                // Temporary `urlString` param until we determine when to pass `sourceUrl`
                // and connect playback events to attribution links
                let urlString = "suno://hook/\(hookId)"
                await send(.deeplink(.hookResponse(urlString: urlString, Result(catching: {
                    try await api.getHookById(hookId)
                }))))
            }

        case .playlist(let playlistId):
            return .run { send in
                await send(.deeplink(.playlistResponse(Result(catching: {
                    try await api.getPlaylistById(0, playlistId, nil, nil)
                }))))
            }

        case .profile(let handle):
            navigationRouter.send(route: .profile(handle))
            return .none

        case .notifications:
            navigationRouter.send(route: .notifications)
            return .none

        case .tiktokShareResult:
            return .none

        case .iosInvite:
            return .none

        case .create:
            createClip(state: &state)
            return .none

        case .createText:
            createClip(state: &state)
            return .none

        case .createAudio:
            createClip(didUseAppShortcut: true, state: &state)
            return .none

        case .createCamera:
            createClip(state: &state)
            return .none

        case .contentShortlink(let shortcode):
            return .run { send in
                await send(.deeplink(.contentShortlinkResponse(Result(catching: {
                    try await api.getShareShortlinkInfo(shortcode)
                }))))
            }

        case .library:
            navigationRouter.send(route: .library(tooltipToShow: nil, showNewClips: true))
            return .none

        case .account:
            state.destination = .subscriptions(.init())
            return .none

        case .remix(let songId, let type, let style, let lyrics):
            return .run { send in
                await send(.deeplink(.remixClipResponse(
                    Result(catching: {
                        try await api.getClip(songId)
                    }),
                    type: type,
                    style: style,
                    lyrics: lyrics
                )))
            }

        case .hooks:
            guard FeatureFlag.hooks.isFeedEnabled else { return .none }
            navigationRouter.send(route: .hooksFeed)
            return .none

        case .webView(let url):
            navigationRouter.send(route: .webView(title: nil, url: url))
            return .none
        }
    }

    
    func handleDeeplinkFromPromoSection(
        _ deeplinkIntent: DeeplinkIntent, 
        promoType: PromoItem.PromoType, 
        promoId: String, 
        state: inout State
    ) -> Effect<Action> {
        let source: ContextSource = {
            switch promoType {
            case .model:
                return .promo(id: promoId)
            case .contest:
                return .contest(clipId: promoId)
            }
        }()
        return handleDeeplink(deeplinkIntent, state: &state, source: source)
    }
    
    func openClipFromResponse(_ result: Result<Clip, Error>, state: inout State) -> Effect<Action> {
        switch result {
        case .success(let clip):
            var effects: [Effect<Action>] = []
            if state.rootTab.selectedTab == .hooks {
                effects.append(.send(.rootTab(.delegate(.navigateToExplore))))
            }
            
            if state.playingClip?.id == clip.id,
               state.playerDestination != nil
            {
                omniplayerClient.setContext(SessionContext(source: .deeplink(entityId: clip.id.remoteId, entityType: .clip)))
                effects.append(.send(.playerDestination(.presented(.omniPlayer(.setExpanded(true))))))
            } else {
                guard !clip.audioUrl.isEmpty else { return .none }
                state.playerDestination = .omniPlayer(
                    .init(
                        clip: clip,
                        queue: [clip],
                        me: state.$me,
                        isExpanded: true,
                        autoStart: true,
                        context: SessionContext(source: .deeplink(entityId: clip.id.remoteId, entityType: .clip))
                    )
                )
                effects.append(.send(.playerDestination(.presented(.omniPlayer(.prepareQueueAndPlay)))))
            }
            
            return .concatenate(effects)
            
        case .failure(let error):
            log.telemetry.error(error)
            return .send(.topToastAction(.show(.warning(L10n.FeatureRoot.fetchClipErrorMessage))))
        }
    }
    
    func openHookFromResponse(_ urlString: String?, _ result: Result<Hook, Error>, state: inout State) -> Effect<Action> {
        switch result {
        case .success(let hook):
            if let hooksState = state.rootTab.hooks,
               let initialHooks = hooksState.hooksFeed.initialHooks,
               initialHooks.contains(where: { $0.id == hook.id }) {
                return .none
            }
            
            return .run { send in
                @Dependency(\.omniplayerClient.pauseCurrentClip) var pauseCurrentClip
                pauseCurrentClip()
                @Dependency(\.eventBus.getOmniplayerChannel) var getOmniplayerChannel
                getOmniplayerChannel().queue(.setExpanded(false))
                await send(.rootTab(.delegate(.navigateToHooksTab)))
                let source = HooksFeedSource.deeplink(sourceUrl: urlString ?? "", hookId: hook.id)
                @Dependency(\.hooksPlayerClient) var hooksPlayerClient
                hooksPlayerClient.playHookInFeed(hook, source)
            }
            
        case .failure(let error):
            log.telemetry.error(error)
            return .send(.topToastAction(.show(.warning(L10n.FeatureRoot.fetchHookErrorMessage))))
        }
    }
    
    func openPlaylistFromResponse(_ result: Result<Playlist, Error>, state: inout State) -> Effect<Action> {
        switch result {
        case .success(let playlist):
            navigationRouter.send(.playlist(playlist))
            return .none
            
        case .failure(let error):
            log.telemetry.error(error)
            return .none
        }
    }
    
    func openContentFromShortlinkResponse(_ result: Result<ShareCodeResponse, Error>, state: inout State) -> Effect<Action> {
        switch result {
        case .success(let shareCodeResponse):
            switch shareCodeResponse.contentType {
            case .song:
                return .run { send in
                    await send(.deeplink(.clipResponse(Result(catching: {
                        try await api.getClip(shareCodeResponse.contentId)
                    }))))
                }
                
            case .playlist:
                return .run { send in
                    await send(.deeplink(.playlistResponse(Result(catching: {
                        try await api.getPlaylistById(0, shareCodeResponse.contentId, nil, nil)
                    }))))
                }
                
            case .profile:
                navigationRouter.send(route: .profile(shareCodeResponse.contentId))
                return .none
                
            case .hook:
                guard FeatureFlag.hooks.isFeedEnabled,
                      FeatureFlag.hooks.hooksDeeplinkingEnabled
                else {
                    return .none
                }
                return .run { send in
                    await send(.deeplink(.hookResponse(urlString: nil, Result(catching: {
                        try await api.getHookById(shareCodeResponse.contentId)
                    }))))
                }
                
            default:
                log.telemetry.error(NavigationError.unhandledNavigationEntity(shareCodeResponse.contentType.rawValue), message: "got a content shortlink, but unknown content type")
                return .none
            }
            
        case .failure(let error):
            log.telemetry.error(error, message: "Failed to get shortlink")
            return .none
        }
    }
    
    func openRemixClipFromResponse(_ result: Result<Clip, Error>, type: String?, style: String?, lyrics: String?, state: inout State) -> Effect<Action> {
        switch result {
        case .success(let clip):
            var effects: [Effect<Action>] = []
            if state.rootTab.selectedTab == .hooks {
                effects.append(.send(.rootTab(.delegate(.navigateToExplore))))
            }
            
            let shouldExpandOmniplayer = state.playerDestination?.omniPlayer?.isCollapsed ?? true
            let openOmniPlayerEffect: Effect<Action> = {
                if state.playingClip?.id == clip.id,
                   let _ = state.playerDestination,
                   shouldExpandOmniplayer
                {
                    omniplayerClient.setContext(SessionContext(source: .deeplink(entityId: clip.id.remoteId, entityType: .clip)))
                    return .send(.playerDestination(.presented(.omniPlayer(.setExpanded(true)))))
                } else {
                    guard !clip.audioUrl.isEmpty else { return .none }
                    state.playerDestination = .omniPlayer(
                        .init(
                            clip: clip,
                            queue: [clip],
                            me: state.$me,
                            isExpanded: true,
                            autoStart: true,
                            context: SessionContext(source: .deeplink(entityId: clip.id.remoteId, entityType: .clip))
                        )
                    )
                    return .send(.playerDestination(.presented(.omniPlayer(.prepareQueueAndPlay))))
                }
            }()
            
            effects.append(openOmniPlayerEffect)
            
            let sleepIfNeededEffect: Effect<Action> = shouldExpandOmniplayer ? .run { _ in 
                @Dependency(\.continuousClock) var clock
                try await clock.sleep(for: .seconds(0.3)) 
            } : .none
            effects.append(sleepIfNeededEffect)
            effects.append(.send(.playerDestination(.presented(.omniPlayer(.expanded(.handleRemixDeeplink(remixType: .init(from: type), style: style, lyrics: lyrics)))))))
            
            return .concatenate(effects)
            
        case .failure(let error):
            log.telemetry.error(error)
            return .send(.topToastAction(.show(.warning(L10n.FeatureRoot.fetchClipErrorMessage))))
        }
    }
}

enum NavigationError: Error {
    case unhandledNavigationEntity(String)
}

