import Env
import Models
import NaturalLanguage
import Network
import SwiftUI

import DesignSystem

@MainActor
public class StatusRowViewModel: ObservableObject {
  let status: Status
  let isFocused: Bool
  let isRemote: Bool
  let showActions: Bool

  @Published var favoritesCount: Int
  @Published var isFavorited: Bool
  @Published var isReblogged: Bool
  @Published var isPinned: Bool
  @Published var isBookmarked: Bool
  @Published var reblogsCount: Int
  @Published var repliesCount: Int
  @Published var embeddedStatus: Status?
  @Published var displaySpoiler: Bool = false
  @Published var isEmbedLoading: Bool = false
  @Published var isFiltered: Bool = false

  @Published var translation: StatusTranslation?
  @Published var isLoadingTranslation: Bool = false
  @Published var showDeleteAlert: Bool = false

  @Published var favoriters: [Account] = []
  @Published var rebloggers: [Account] = []

  @Published var isLoadingRemoteContent: Bool = false
  @Published var localStatusId: String?
  @Published var localStatus: Status?

  private let theme = Theme.shared

  private var seen = false

  var filter: Filtered? {
    status.reblog?.filtered?.first ?? status.filtered?.first
  }

  var highlightRowColor: Color {
    if status.visibility == .direct {
      return theme.tintColor.opacity(0.15)
    } else if status.userMentioned != nil {
      return theme.secondaryBackgroundColor
    } else {
      return theme.primaryBackgroundColor
    }
  }

  let client: Client
  let routerPath: RouterPath

  public init(status: Status,
              client: Client,
              routerPath: RouterPath,
              isFocused: Bool = false,
              isRemote: Bool = false,
              showActions: Bool = true)
  {
    self.status = status
    self.client = client
    self.routerPath = routerPath
    self.isFocused = isFocused
    self.isRemote = isRemote
    self.showActions = showActions
    if let reblog = status.reblog {
      isFavorited = reblog.favourited == true
      isReblogged = reblog.reblogged == true
      isPinned = reblog.pinned == true
      isBookmarked = reblog.bookmarked == true
    } else {
      isFavorited = status.favourited == true
      isReblogged = status.reblogged == true
      isPinned = status.pinned == true
      isBookmarked = status.bookmarked == true
    }
    favoritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
    reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
    repliesCount = status.reblog?.repliesCount ?? status.repliesCount
    if UserPreferences.shared.autoExpandSpoilers {
      displaySpoiler = false
    } else {
      displaySpoiler = !(status.reblog?.spoilerText.asRawText ?? status.spoilerText.asRawText).isEmpty
    }

    isFiltered = filter != nil
  }

  func markSeen() {
    // called in on appear so we can cache that the status has been seen.
    if UserPreferences.shared.suppressDupeReblogs && !seen {
      DispatchQueue.global().async { [weak self] in
        guard let self else { return }
        ReblogCache.shared.cache(self.status, seen: true)
        Task { @MainActor in
          self.seen = true
        }
      }
    }
  }

  func navigateToDetail() {
    guard !isFocused else { return }
    if isRemote, let url = URL(string: status.reblog?.url ?? status.url ?? "") {
      routerPath.navigate(to: .remoteStatusDetail(url: url))
    } else {
      routerPath.navigate(to: .statusDetailWithStatus(status: status.reblogAsAsStatus ?? status))
    }
  }

  func navigateToAccountDetail(account: Account) {
    if isRemote, let url = account.url {
      withAnimation {
        isLoadingRemoteContent = true
      }
      Task {
        await routerPath.navigateToAccountFrom(url: url)
        isLoadingRemoteContent = false
      }
    } else {
      routerPath.navigate(to: .accountDetailWithAccount(account: account))
    }
  }

  func navigateToMention(mention: Mention) {
    if isRemote {
      withAnimation {
        isLoadingRemoteContent = true
      }
      Task {
        await routerPath.navigateToAccountFrom(url: mention.url)
        isLoadingRemoteContent = false
      }
    } else {
      routerPath.navigate(to: .accountDetail(id: mention.id))
    }
  }

  func loadEmbeddedStatus() async {
    guard embeddedStatus == nil,
          !status.content.statusesURLs.isEmpty,
          let url = status.content.statusesURLs.first,
          client.hasConnection(with: url)
    else {
      if isEmbedLoading {
        isEmbedLoading = false
      }
      return
    }
    do {
      isEmbedLoading = true
      var embed: Status?
      if url.absoluteString.contains(client.server), let id = Int(url.lastPathComponent) {
        embed = try await client.get(endpoint: Statuses.status(id: String(id)))
      } else {
        let results: SearchResults = try await client.get(endpoint: Search.search(query: url.absoluteString,
                                                                                  type: "statuses",
                                                                                  offset: 0,
                                                                                  following: nil),
                                                          forceVersion: .v2)
        embed = results.statuses.first
      }
      withAnimation {
        embeddedStatus = embed
        isEmbedLoading = false
      }
    } catch {
      isEmbedLoading = false
    }
  }

  func favorite() async {
    guard client.isAuth else { return }
    isFavorited = true
    favoritesCount += 1
    do {
      let status: Status = try await client.post(endpoint: Statuses.favorite(id: localStatusId ?? status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isFavorited = false
      favoritesCount -= 1
    }
  }

  func unFavorite() async {
    guard client.isAuth else { return }
    isFavorited = false
    favoritesCount -= 1
    do {
      let status: Status = try await client.post(endpoint: Statuses.unfavorite(id: localStatusId ?? status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isFavorited = true
      favoritesCount += 1
    }
  }

  func reblog() async {
    guard client.isAuth else { return }
    isReblogged = true
    reblogsCount += 1
    do {
      let status: Status = try await client.post(endpoint: Statuses.reblog(id: localStatusId ?? status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isReblogged = false
      reblogsCount -= 1
    }
  }

  func unReblog() async {
    guard client.isAuth else { return }
    isReblogged = false
    reblogsCount -= 1
    do {
      let status: Status = try await client.post(endpoint: Statuses.unreblog(id: localStatusId ?? status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isReblogged = true
      reblogsCount += 1
    }
  }

  func pin() async {
    guard client.isAuth else { return }
    isPinned = true
    do {
      let status: Status = try await client.post(endpoint: Statuses.pin(id: status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isPinned = false
    }
  }

  func unPin() async {
    guard client.isAuth else { return }
    isPinned = false
    do {
      let status: Status = try await client.post(endpoint: Statuses.unpin(id: status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isPinned = true
    }
  }

  func bookmark() async {
    guard client.isAuth else { return }
    isBookmarked = true
    do {
      let status: Status = try await client.post(endpoint: Statuses.bookmark(id: localStatusId ?? status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isBookmarked = false
    }
  }

  func unbookmark() async {
    guard client.isAuth else { return }
    isBookmarked = false
    do {
      let status: Status = try await client.post(endpoint: Statuses.unbookmark(id: localStatusId ?? status.reblog?.id ?? status.id))
      updateFromStatus(status: status)
    } catch {
      isBookmarked = true
    }
  }

  func delete() async {
    do {
      _ = try await client.delete(endpoint: Statuses.status(id: status.id))
    } catch {}
  }

  func fetchActionsAccounts() async {
    do {
      favoriters = try await client.get(endpoint: Statuses.favoritedBy(id: status.id, maxId: nil))
      rebloggers = try await client.get(endpoint: Statuses.rebloggedBy(id: status.id, maxId: nil))
    } catch {}
  }

  private func updateFromStatus(status: Status) {
    if let reblog = status.reblog {
      isFavorited = reblog.favourited == true
      isReblogged = reblog.reblogged == true
      isPinned = reblog.pinned == true
      isBookmarked = reblog.bookmarked == true
    } else {
      isFavorited = status.favourited == true
      isReblogged = status.reblogged == true
      isPinned = status.pinned == true
      isBookmarked = status.bookmarked == true
    }
    favoritesCount = status.reblog?.favouritesCount ?? status.favouritesCount
    reblogsCount = status.reblog?.reblogsCount ?? status.reblogsCount
    repliesCount = status.reblog?.repliesCount ?? status.repliesCount
  }

  func getStatusLang() -> String? {
    status.reblog?.language ?? status.language
  }

  func translate(userLang: String) async {
    await translate(userLang: userLang, sourceLang: getStatusLang())
  }

  private func translate(userLang: String, sourceLang _: String?) async {
    do {
      withAnimation {
        isLoadingTranslation = true
      }
      // We first use instance translation API if available.
      let translation: StatusTranslation = try await client.post(endpoint: Statuses.translate(id: status.reblog?.id ?? status.id,
                                                                                              lang: userLang))
      withAnimation {
        self.translation = translation
        isLoadingTranslation = false
      }
    } catch {
      // If not or fail we use Ice Cubes own DeepL client.
      let deepLClient = DeepLClient()
      let translation = try? await deepLClient.request(target: userLang,
                                                       source: status.language,
                                                       text: status.reblog?.content.asRawText ?? status.content.asRawText)
      withAnimation {
        self.translation = translation
        isLoadingTranslation = false
      }
    }
  }

  func fetchRemoteStatus() async -> Bool {
    guard isRemote, let remoteStatusURL = URL(string: status.reblog?.url ?? status.url ?? "") else { return false }
    isLoadingRemoteContent = true
    let results: SearchResults? = try? await client.get(endpoint: Search.search(query: remoteStatusURL.absoluteString,
                                                                                type: "statuses",
                                                                                offset: nil,
                                                                                following: nil),
                                                        forceVersion: .v2)
    if let status = results?.statuses.first {
      localStatusId = status.id
      localStatus = status
      isLoadingRemoteContent = false
      return true
    } else {
      isLoadingRemoteContent = false
      return false
    }
  }
}
