import BackendEnvironmentClient
import Foundation
import StatsigClient

public enum DeeplinkIntent: Equatable {
    case song(songId: String, sh: String?) // "/song/{songId}?sh={share_attribution_id}"
    case playlist(playlistId: String) // "/playlist/{playlistId}"
    case profile(profileId: String) // "/profile/{profileId}"
    case profileHook(userHandle: String, hookId: String) // "/@{userHandle}/hook/{hookId}"
    case notifications // "/notifications"
    case tiktokShareResult(status: ShareStatus) // "/tiktok/{status}"
    case iosInvite(promoCode: String) // "/ios-invite/{myPromoCode}"
    case create // "/create"
    case createText // "/create-text"
    case createAudio // "/create-audio"
    case createCamera // "/create-camera"
    case contentShortlink(shortcode: String) // "/s/<shortcode>
    case library // "/library"
    case remix(songId: String, type: String?, style: String?, lyrics: String?) // "/remix?song_id={song_id}&type={remix_type}&style={style}&lyrics={lyrics}"
    case hook(hookId: String) // "/hook/{hookId}"
    case hooks // "/hooks"
    case account // "/account"
    case webView(url: URL) // For unsupported URLs that we want to open in a webview

    // swiftlint:disable:next cyclomatic_complexity
    init?(path: [String], queryParams: [String: String]) {
        guard !path.isEmpty else { return nil }

        switch path[0] {
        case "song":
            guard path.count > 1 else { return nil }
            let sh = queryParams["sh"]
            self = .song(songId: path[1], sh: sh)

        case "playlist":
            guard path.count > 1 else { return nil }
            self = .playlist(playlistId: path[1])

        case "profile":
            guard path.count > 1 else { return nil }
            self = .profile(profileId: path[1])

        case "notifications":
            self = .notifications

        case "tiktok":
            let status = Self.handleTiktokShareResult(queryParams: queryParams)
            self = .tiktokShareResult(status: status)

        case "ios-invite":
            guard path.count > 1 else { return nil }
            self = .iosInvite(promoCode: path[1])

        case "create":
            self = .create

        case "create-text":
            self = .createText

        case "create-audio":
            self = .createAudio

        case "create-camera":
            self = .createCamera

        case "s":
            guard path.count > 1 else { return nil }
            self = .contentShortlink(shortcode: path[1])

        case "library", "me":
            self = .library

        case "account":
            self = .account

        case "remix":
            /// `songId` is considered a mandatory query parameter on the iOS side, and missing it is considered undefined behavior and should not be handled.
            guard let songId = queryParams["song_id"] else {
                return nil
            }
            let type = queryParams["type"]
            let style = queryParams["style"]
            let lyrics = queryParams["lyrics"]
            self = .remix(songId: songId, type: type, style: style, lyrics: lyrics)

        case let pathString where pathString.hasPrefix("@"):
            let user = pathString.dropFirst()
            if path.count > 2 {
                switch path[1] {
                case "hook":
                    guard FeatureFlag.hooks.isFeedEnabled,
                          FeatureFlag.hooks.hooksDeeplinkingEnabled
                    else {
                        return nil
                    }
                    self = .profileHook(userHandle: String(user), hookId: path[2])

                default:
                    self = .profile(profileId: String(user))
                }
            } else {
                self = .profile(profileId: String(user))
            }

        case "hooks":
            guard FeatureFlag.hooks.isFeedEnabled else { return nil }
            guard path.count == 1 else { return nil }
            self = .hooks

        case "hook":
            guard FeatureFlag.hooks.isFeedEnabled,
                  FeatureFlag.hooks.hooksDeeplinkingEnabled
            else {
                return nil
            }
            guard path.count > 1 else { return nil }
            self = .hook(hookId: path[1])

        default:
            return nil
        }
    }

    public init?(url: URL) {
        guard let intent = DeeplinkIntent(url: url, forceV2: false) else {
            return nil
        }
        self = intent
    }

    init?(url: URL, forceV2: Bool) {
        guard FeatureFlag.general.deepLinkIntentsV2 || forceV2 else {
            guard let v1Parsed = DeeplinkIntent.parse(url: url) else { return nil }
            self = v1Parsed; return
        }

        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
            return nil
        }
        let queryParams = components.queryItems?.asStringDict ?? [:]
        let pathComponents = components.path.split(separator: "/").map(String.init)

        switch components.scheme {
        case "http", "https":
            /// Here we handle web app URLs
            guard components.host?.isValidSunoHostname == true else {
                return nil
            }
            if let parsed = Self(path: pathComponents, queryParams: queryParams) {
                self = parsed
            } else {
                self = .webView(url: url)
            }

        case Bundle.main.urlScheme:
            /// Here we handle the app's URL scheme eg. suno://song/123
            let path = ([components.host] + pathComponents).compactMap { $0 }
            self.init(path: path + pathComponents, queryParams: queryParams)

        case .none:
            /// We can also try to handle applinks w/ no https or www
            /// e.g. `b.suno.fm/notifications` or `suno.com/notifications`
            /// These links still deeplink into our app
            guard url.pathComponents.count > 1,
                  url.pathComponents.first?.isValidSunoHostname == true
            else {
                return nil
            }

            self.init(path: Array(url.pathComponents.dropFirst()), queryParams: queryParams)

        case .some:
            // Unsupported
            return nil
        }
    }

    public var urlPath: String {
        switch self {
        case let .song(songId, sh):
            let urlPath = "song/\(songId)"
            guard let sh = sh?.ifNotEmpty else { return urlPath }
            return urlPath.appending("?sh=\(sh)")

        case let .playlist(playlistId):
            return "playlist/\(playlistId)"

        case let .profile(profileId):
            return "profile/\(profileId)"

        case .notifications:
            return "notifications"

        case let .tiktokShareResult(status):
            return "tiktok/\(status)"

        case let .iosInvite(promoCode):
            return "ios-invite/\(promoCode)"

        case .create:
            return "create"

        case .createText:
            return "create-text"

        case .createAudio:
            return "create-audio"

        case .createCamera:
            return "create-camera"

        case .library:
            return "library"

        case .account:
            return "account"

        case .contentShortlink(shortcode: let shortcode):
            return "s/\(shortcode)"

        case let .remix(songId, type, style, lyrics):
            var components: [String] = []
            components.append("song_id=\(songId)")
            if let type = type { components.append("type=\(type)") }
            if let style = style { components.append("style=\(style)") }
            if let lyrics = lyrics { components.append("lyrics=\(lyrics)") }
            return "remix?\(components.joined(separator: "&"))"

        case .hooks:
            return "hooks"

        case let .hook(hookId):
            return "hook/\(hookId)"

        case let .profileHook(userHandle, hookId):
            return "@\(userHandle)/hook/\(hookId)"
            
        case let .webView(url):
            return url.absoluteString
        }
    }

    private static func parse(url: URL) -> Self? {
        if let host = Optional(BackendEnvironmentProvider.currentConfiguration().webEndpointHost) {
            if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
               components.host == host
            {
                let pathComponents = components.path.split(separator: "/").map(String.init)
                let queryParams = components.queryItems?.reduce(into: [String: String]()) { result, item in
                    if let value = item.value {
                        result[item.name] = value
                    }
                } ?? [:]
                return Self(path: pathComponents, queryParams: queryParams)
            } else if url.pathComponents.count > 1, url.pathComponents.first == host {
                /// We can also try to handle applinks w/ no https or www
                /// e.g. `b.suno.fm/notifications` or `suno.com/notifications`
                /// These links still deeplink into our app
                return Self(path: Array(url.pathComponents.dropFirst()), queryParams: [:])
            }
        } else if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
                  components.scheme == "suno",
                  let host = components.host
        {
            return Self(path: [host], queryParams: [:])
        }

        return nil
    }

    /// Parse a URL to see if it's a suno.com URL that can be handled internally via deeplinks
    public static func createDeeplinkIntent(from url: URL) -> DeeplinkIntent? {
        // Check if this is a suno.com or b.suno.fm URL that we can handle internally
        guard let host = url.host?.lowercased(),
              host.contains("suno.com") || host.contains("suno.fm")
        else {
            return nil
        }

        // Try to parse it as a deeplink intent
        return DeeplinkIntent(url: url)
    }
}

private extension Array where Element == URLQueryItem {
    var asStringDict: [String: String] {
        reduce(into: [String: String]()) { result, item in
            guard let value = item.value else { return }
            result[item.name] = value
        }
    }
}

private extension String {
    var isValidSunoHostname: Bool {
        let webBaseURL = BackendEnvironmentProvider.currentConfiguration().webEndpointHost
        return self == webBaseURL || hasSuffix(".\(webBaseURL)")
    }
}
