' Copyright (C) Andy https://github.com/AAndyProgram
' This program is free software: you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation, either version 3 of the License, or
' (at your option) any later version.
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports System.Threading
Imports SCrawler.API.Base
Imports SCrawler.API.YouTube.Objects
Imports PersonalUtilities.Functions.XML
Imports PersonalUtilities.Functions.RegularExpressions
Imports PersonalUtilities.Tools.Web.Documents.JSON
Imports UTypes = SCrawler.API.Base.UserMedia.Types
Imports UStates = SCrawler.API.Base.UserMedia.States
Namespace API.Bluesky
    Friend Class UserData : Inherits UserDataBase
#Region "XML names"
        Private Const Name_DownloadModelMedia As String = "DownloadModelMedia"
        Private Const Name_DownloadModelProfile As String = "DownloadModelProfile"
        Private Const Name_ForceParseProfileInfo As String = "ForceParseProfileInfo"
#End Region
#Region "Declarations"
        Private ReadOnly Property MySettings As SiteSettings
            Get
                Return HOST.Source
            End Get
        End Property
        Private ReadOnly Property ID_Encoded As String
            Get
                Return If(ID.IsEmptyString, String.Empty, SymbolsConverter.ASCII.EncodeSymbolsOnly(ID))
            End Get
        End Property
        Private ReadOnly _TmpPosts2 As List(Of String)
        Friend Property DownloadModelMedia As Boolean = True
        Friend Property DownloadModelProfile As Boolean = False
        Private Property ForceParseProfileInfo As Boolean = False
#End Region
#Region "Loader"
        Private Sub UpdateUserOptions()
            If ID.IsEmptyString AndAlso Not Name.IsEmptyString AndAlso Name.StartsWith("did@") Then
                NameTrue = Name.Replace("@", ":")
                ID = NameTrue
                ForceParseProfileInfo = True
            End If
        End Sub
        Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean)
            With Container
                If Loading Then
                    If .Contains(Name_DownloadModelMedia) Then
                        DownloadModelMedia = .Value(Name_DownloadModelMedia).FromXML(Of Boolean)(True)
                        DownloadModelProfile = .Value(Name_DownloadModelProfile).FromXML(Of Boolean)(False)
                    Else
                        DownloadModelMedia = ParseUserMediaOnly
                        DownloadModelProfile = Not ParseUserMediaOnly
                    End If
                    ForceParseProfileInfo = .Value(Name_ForceParseProfileInfo).FromXML(Of Boolean)(False)
                Else
                    If ID.IsEmptyString Then
                        UpdateUserOptions()
                        .Value(Name_UserID) = ID
                    End If
                    .Add(Name_DownloadModelMedia, DownloadModelMedia.BoolToInteger)
                    .Add(Name_DownloadModelProfile, DownloadModelProfile.BoolToInteger)
                    .Add(Name_ForceParseProfileInfo, ForceParseProfileInfo.BoolToInteger)
                End If
            End With
        End Sub
        Friend Overrides Function ExchangeOptionsGet() As Object
            Return New EditorExchangeOptions(Me)
        End Function
        Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
            If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptions Then DirectCast(Obj, EditorExchangeOptions).Apply(Me)
        End Sub
#End Region
#Region "Initializer"
        Friend Sub New()
            UseInternalM3U8Function = True
            _TmpPosts2 = New List(Of String)
        End Sub
#End Region
#Region "Token"
        Private Function UpdateToken(Optional ByVal Force As Boolean = False, Optional ByVal OnlyAddHeader As Boolean = False) As Boolean
            Dim process As Boolean = True
            If CDate(MySettings.TokenUpdateTime.Value).AddHours(2) <= Now Or Force Then
                process = MySettings.UpdateToken(Force)
                If process Then _TokenUpdateCount += 1
            End If
            If process Or OnlyAddHeader Then Responser.Headers.Add("authorization", MySettings.Token.Value)
            Return Not Responser.Headers.Value("authorization").IsEmptyString
        End Function
        Private _TokenUpdateCount As Integer = 0
        Private Sub TokenUpdateCountReset()
            _TokenUpdateCount = 0
        End Sub
#End Region
#Region "Download"
        Private _PostCount As Integer = 0
        Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
            _TmpPosts2.Clear()
            Try
                If Not DownloadModelMedia And Not DownloadModelProfile Then
                    DownloadModelMedia = True
                ElseIf DownloadModelMedia Then
                    DownloadModelProfile = False
                Else
                    DownloadModelMedia = False
                End If
                If Not CBool(MySettings.CookiesEnabled.Value) Then Responser.Cookies.Clear()
                UpdateToken(, True)
                _TokenUpdateCount = 0
                _PostCount = 0
                DownloadData(String.Empty, Token)
            Finally
                _TempPostsList.ListAddList(_TmpPosts2, LNC)
                _TmpPosts2.Clear()
            End Try
        End Sub
        Private Overloads Sub DownloadData(ByVal Cursor As String, ByVal Token As CancellationToken)
            Dim URL$ = String.Empty
            Try
                If (Not IsSavedPosts And ID.IsEmptyString) Or ForceParseProfileInfo Then GetProfileInfo(Token)
                If Not IsSavedPosts And ID.IsEmptyString Then Throw New ArgumentNullException("ID", "ID is null")
                If UpdateToken() Then
                    Dim nextCursor$ = String.Empty
                    Dim c%
                    Dim n$(), p$()
                    If IsSavedPosts Then
                        URL = "https://bsky.social/xrpc/app.bsky.bookmark.getBookmarks"
                        If Not Cursor.IsEmptyString Then URL &= $"?cursor={Cursor}"
                        n = {"bookmarks"}
                        p = {"item"}
                    Else
                        'posts_and_author_threads
                        'posts_with_media
                        URL = $"https://bsky.social/xrpc/app.bsky.feed.getAuthorFeed?actor={ID_Encoded}&filter={IIf(DownloadModelMedia, "posts_with_media", "posts_and_author_threads")}&includePins=false&limit=99"
                        If Not Cursor.IsEmptyString Then URL &= $"&cursor={SymbolsConverter.ASCII.EncodeSymbolsOnly(Cursor)}"
                        n = {"feed"}
                        p = {"post"}
                    End If
                    Dim r$ = Responser.GetResponse(URL)
                    TokenUpdateCountReset()
                    If Not r.IsEmptyString Then
                        Using j As EContainer = JsonDocument.Parse(r)
                            If j.ListExists Then
                                nextCursor = j.Value("cursor")
                                With j(n)
                                    If .ListExists Then
                                        For Each post As EContainer In .Self
                                            With post(p)
                                                c = DefaultParser(.Self) ',, nextCursor)
                                                Select Case c
                                                    Case CInt(DateResult.Skip) * -1 : Continue For
                                                    Case CInt(DateResult.Exit) * -1 : Exit Sub
                                                    Case Is > 0 : _PostCount += c
                                                End Select
                                                If DownloadTopCount.HasValue AndAlso DownloadTopCount.Value <= _PostCount Then Exit Sub
                                            End With
                                        Next
                                    ElseIf IsSavedPosts Then
                                        nextCursor = String.Empty
                                    End If
                                End With
                            End If
                        End Using

                        If Not nextCursor.IsEmptyString Then DownloadData(nextCursor, Token)
                    End If
                End If
            Catch ex As Exception
                ProcessException(ex, Token, $"DownloadData({URL})")
            End Try
        End Sub
#End Region
#Region "DefaultParser"
        Private Const Down_ImageAddress As String = "https://cdn.bsky.app/img/feed_fullsize/plain/{0}/{1}"
        Private Function GetPostID(ByVal PostUri As String) As String
            Return If(PostUri.IsEmptyString, String.Empty, PostUri.Split("/").LastOrDefault)
        End Function
        Private Function DefaultParser(ByVal e As EContainer, Optional ByVal CheckDateLimits As Boolean = True, Optional ByRef NextCursor As String = Nothing,
                                       Optional ByVal CheckTempPosts As Boolean = True, Optional ByVal State As UStates = UStates.Unknown) As Integer
            Const exitReturn% = CInt(DateResult.Exit) * -1
            Const skipReturn% = CInt(DateResult.Skip) * -1
            Dim postID$, postDate$, __url$, __urlBase$, __txt$, __userId$, __postAuthor$
            Dim updateUrl As Boolean
            Dim c% = 0
            Dim m As UserMedia
            Dim d As EContainer
            With e
                If .ListExists Then
                    postID = GetPostID(.Value("uri"))
                    postDate = String.Empty
                    __urlBase = String.Empty
                    __txt = String.Empty
                    __userId = .Value({"author"}, "did")
                    __postAuthor = String.Empty
                    With .Item({"record"})
                        If .ListExists Then
                            '2025-01-28T02:42:12.415Z
                            postDate = .Value("createdAt")
                            If Not IsSavedPosts Then NextCursor = postDate
                            If CheckDateLimits Then
                                Select Case CheckDatesLimit(postDate, DateProvider)
                                    Case DateResult.Skip : Return skipReturn 'Continue For
                                    Case DateResult.Exit : Return exitReturn 'Exit Sub
                                End Select
                            End If

                            If CheckTempPosts Then
                                'If _TempPostsList.Contains(postID) Then Return exitReturn Else _TempPostsList.Add(postID)
                                If _TempPostsList.Contains(postID) Then Return exitReturn Else _TmpPosts2.Add(postID)
                            End If

                            If ParseUserMediaOnly And Not IsSavedPosts And Not ID.IsEmptyString And Not __userId.IsEmptyString And Not ID = __userId Then Return skipReturn

                            __postAuthor = e.Value({"author"}, "did")
                            __urlBase = $"https://bsky.app/profile/{If(IsSavedPosts, __postAuthor, NameTrue)}/post/{postID}"
                        End If
                    End With

                    Dim createMedia As Func(Of String, UTypes, UserMedia) =
                        Function(ByVal url As String, ByVal type As UTypes) As UserMedia
                            m = New UserMedia(url, type) With {
                                .URL_BASE = __urlBase,
                                .File = CreateFileFromUrl(url, type),
                                .Post = New UserPost(postID, If(AConvert(Of Date)(postDate, DateProvider, Nothing, EDP.ReturnValue), Nothing)),
                                .State = State,
                                .PostText = __txt,
                                .PostTextFileSpecialFolder = DownloadTextSpecialFolder
                            }
                            If type = UTypes.Text Then m.PostTextFile = $"{postID}.txt"
                            _TempMediaList.ListAddValue(m, LNC)
                            c += 1
                            Return m
                        End Function

                    __txt = .Value({"record"}, "text").IfNullOrEmpty(__txt)

                    For Each SecondExtraction As Boolean In {False, True}
                        With If(SecondExtraction, .Item({"record", "embed"}), .Item("embed"))
                            If .ListExists Then

                                If If(.Item("images")?.Count, 0) > 0 Then
                                    With .Item("images")
                                        For Each d In .Self
                                            updateUrl = False
                                            __url = d.Value("fullsize")
                                            If __url.IsEmptyString Then __url = d.Value({"image", "ref"}, "$link") : updateUrl = True
                                            If __url.IsEmptyString And SecondExtraction Then updateUrl = False : __url = e.Value({"embed"}, "thumb")
                                            If Not __url.IsEmptyString Then
                                                If updateUrl AndAlso Not __url.StartsWith("http") Then _
                                                   __url = $"https://cdn.bsky.app/img/feed_fullsize/plain/{__postAuthor}/{__url}@jpeg"
                                                createMedia(__url, UTypes.Picture)
                                            End If
                                        Next
                                    End With
                                End If

                                If Not .Value("playlist").IsEmptyString Then createMedia(.Value("playlist"), UTypes.m3u8)

                                If If(.Item("external")?.Count, 0) > 0 Then
                                    __txt = .Value({"external"}, "title").IfNullOrEmpty(__txt)
                                    createMedia(.Value({"external"}, "uri"), UTypes.GIF)
                                End If

                                If If(.Item({"media"}, "external")?.Count, 0) > 0 Then
                                    __txt = .Value({"media", "external"}, "title").IfNullOrEmpty(__txt)
                                    createMedia(.Value({"media", "external"}, "uri"), UTypes.GIF)
                                End If
                            End If
                        End With

                        If c > 0 Then Exit For
                    Next
                End If
            End With
            Return c
        End Function
#End Region
#Region "GetProfileInfo"
        Private Sub GetProfileInfo(ByVal Token As CancellationToken)
            Try
                If ForceParseProfileInfo Then ForceParseProfileInfo = False : _ForceSaveUserInfo = True
                If UpdateToken() Then
                    Dim r$ = Responser.GetResponse($"https://bsky.social/xrpc/app.bsky.actor.getProfile?actor={ID.IfNullOrEmpty(NameTrue)}")
                    TokenUpdateCountReset()
                    If Not r.IsEmptyString Then
                        Using j As EContainer = JsonDocument.Parse(r)
                            If j.ListExists Then
                                ID = j.Value("did")
                                UserSiteNameUpdate(j.Value("displayName"))
                                UserDescriptionUpdate(j.Value("description"))
                                NameTrue = j.Value("handle")
                                SimpleDownloadAvatar(j.Value("avatar"))
                                SimpleDownloadAvatar(j.Value("banner"))
                            End If
                        End Using
                    End If
                Else
                    Throw New ArgumentException("Token is null", "Token")
                End If
            Catch ex As Exception
                ProcessException(ex, Token, "GetProfileInfo")
            End Try
        End Sub
#End Region
#Region "ReparseMissing"
        Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken)
            Const uriPattern$ = "at://{0}/app.bsky.feed.post/{1}"
            Dim rList As New List(Of Integer)
            Try
                If ContentMissingExists AndAlso UpdateToken() Then
                    Dim r$, url$, uri$
                    Dim tu As Byte
                    Dim m As UserMedia
                    Dim j As EContainer
                    For i% = 0 To _ContentList.Count - 1
                        m = _ContentList(i)
                        If m.State = UStates.Missing Then
                            uri = SymbolsConverter.ASCII.EncodeSymbolsOnly(String.Format(uriPattern, NameTrue, m.Post.ID))
                            url = $"https://bsky.social/xrpc/app.bsky.feed.getPostThread?uri={uri}&depth=10"
                            For tu = 0 To 1
                                Try
                                    Responser.ResetStatus()
                                    r = Responser.GetResponse(url)
                                    TokenUpdateCountReset()
                                    If Not r.IsEmptyString Then
                                        j = JsonDocument.Parse(r)
                                        If j.ListExists Then
                                            If DefaultParser(j({"thread", "post"}), False,, False, UStates.Missing) > 0 Then rList.Add(i)
                                            j.Dispose()
                                        End If
                                    End If
                                    Exit For
                                Catch eex As Exception
                                    If ProcessException(eex, Token, $"ReparseMissing({url})",,, False) <> 1 Then Throw eex
                                End Try
                            Next
                        End If
                    Next
                Else
                    Throw New ArgumentException("Token is null", "Token")
                End If
            Catch ex As Exception
                ProcessException(ex, Token, "ReparseMissing error")
            Finally
                If rList.Count > 0 Then
                    For i% = rList.Count - 1 To 0 Step -1 : _ContentList.RemoveAt(rList(i)) : Next
                    rList.Clear()
                End If
            End Try
        End Sub
#End Region
#Region "CreateFileFromUrl"
        Protected Overloads Overrides Function CreateFileFromUrl(ByVal URL As String) As SFile
            Return CreateFileFromUrl(URL, UTypes.Undefined)
        End Function
        Protected Overloads Function CreateFileFromUrl(ByVal URL As String, ByVal Type As UTypes) As SFile
            Dim f As SFile = MyBase.CreateFileFromUrl(URL)
            Dim force As Boolean = False
            f.Separator = "\"
            With URL.Split("/")
                If .ListExists Then
                    With DirectCast(RegexReplace(.Last, RegEx_FilePattern), List(Of String))
                        If .ListExists(4) Then
                            f.Name = .Item(1).IfNullOrEmpty(f.Name)
                            f.Extension = .Item(3)
                        End If
                    End With
                End If
            End With
            If Not f.Extension.IsEmptyString AndAlso f.Extension.ToLower = "m3u8" Then force = True : Type = UTypes.m3u8
            If f.Extension.IsEmptyString Or force Then
                Select Case Type
                    Case UTypes.Picture : f.Extension = "jpg"
                    Case UTypes.GIF : f.Extension = "gif"
                    Case UTypes.m3u8 : f.Name = "Video" : f.Extension = "mp4"
                End Select
            End If
            Return f
        End Function
#End Region
#Region "DownloadContent"
        Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken)
            DownloadContentDefault(Token)
        End Sub
        Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile
            Return M3U8.Download(URL, DestinationFile, Token, Progress, Not IsSingleObjectDownload)
        End Function
        Protected Overrides Function DownloadContentDefault_ConvertWebp(ByVal m As UserMedia, ByVal Process As Boolean) As SFile
            Return DownloadContentDefault_ConvertWebp_TestImg(m, Process)
        End Function
#End Region
#Region "DownloadSingleObject"
        Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
            _TokenUpdateCount = 0
            UpdateToken()
            Dim l As List(Of String) = RegexReplace(Data.URL, RegEx_SinglePostPattern)
            If l.ListExists(3) Then
                NameTrue = l(1)
                _ContentList.Add(New UserMedia(Data.URL) With {.State = UStates.Missing, .Post = l(2)})
                ReparseMissing(Token)
            End If
            MyBase.DownloadSingleObject_GetPosts(Data, Token)
        End Sub
#End Region
#Region "Exception"
        Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False,
                                                          Optional ByVal EObj As Object = Nothing) As Integer
            If Responser.StatusCode = Net.HttpStatusCode.BadRequest Then '400
                If _TokenUpdateCount = 0 AndAlso UpdateToken(True) Then
                    Return 1
                Else
                    Return 0
                End If
            Else
                Return 0
            End If
        End Function
#End Region
#Region "IDisposable Support"
        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If disposing Then _TmpPosts2.Clear()
            MyBase.Dispose(disposing)
        End Sub
#End Region
    End Class
End Namespace