(*
 *    _  _   ____                         _  
 *  _| || |_/ ___|  ___ _ __  _ __   ___ | | 
 * |_  ..  _\___ \ / _ \ '_ \| '_ \ / _ \| | 
 * |_      _|___) |  __/ |_) | |_) | (_) |_| 
 *   |_||_| |____/ \___| .__/| .__/ \___/(_) 
 *                     |_|   |_|             
 *
 * Personal Social Web.
 *
 * as2_test.ml
 *
 * Copyright (C) The #Seppo contributors. All rights reserved.
 *
 * 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; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *)


open Seppo_lib
module A = Assrt

let test_follower_state () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_follower_state");
  Ap.Followers.State.(Pending |> to_string) |>
  A.equals_string __LOC__ "pending"

let test_inbox_follow () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_follow");
  let a = File.in_channel "data/follow.mastodon.json" Ezjsonm.from_channel
          |> As2_vocab.Activitypub.Decode.follow
          |> Result.get_ok in
  a.id |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/afe4ac8a-aaeb-41f5-a348-e5d133bdb931");
  a.actor |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/users/traunstein");
  a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.social/2023-06-12/activitypub/profile.json");
  match a.state with
  | None -> ()
  | _ -> "-" |> A.equals_string __LOC__ ""

let test_inbox_unfollow () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_unfollow");
  let o = File.in_channel "data/undo-follow.mastodon.json" Ezjsonm.from_channel
          |> As2_vocab.Activitypub.Decode.(undo follow)
          |> Result.get_ok in
  o.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein#follows/5219/undo";
  o.actor |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein";
  o.obj.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/a5fc3ab5-92f3-4641-a33e-96418e9bec78";
  o.obj.object_ |> Uri.to_string |> A.equals_string __LOC__ "https://dev.seppo.social/2023-06-12/activitypub/profile.json";
  assert (o.actor |> Uri.equal o.obj.actor);
(*
  a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.social/2023-06-12/activitypub/profile.json");
  match a.state with
  | None -> ()
  | _ -> "-" |> A.equals_string __LOC__ ""
*)
  assert true

let test_followers_page_json_raw () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json_raw");
  let fs = [
    "1/" |> Uri.of_string;
    "2/" |> Uri.of_string;
    "3/" |> Uri.of_string;
    "4/" |> Uri.of_string;
    "5/" |> Uri.of_string;
  ] in
  let base' = "https://example.com/u/" |> Uri.of_string in
  let base = "https://example.com/ap/followers/2.jsa" |> Uri.of_string in
  let p : Uri.t As2_vocab.Types.collection_page = {
    id         = base;
    current    = Some base;
    first      = None;
    is_ordered = true;
    items      = fs;
    last       = Some (Http.reso ~base (Uri.of_string "0.jsa"));
    next       = Some (Http.reso ~base (Uri.of_string "1.jsa"));
    part_of    = Some (Http.reso ~base (Uri.of_string "index.jsa"));
    prev       = Some (Http.reso ~base (Uri.of_string "3.jsa"));
    total_items= None;
  } in
  p |> As2_vocab.Encode.(collection_page ~base (uri ~base:base'))
  |> Ezjsonm.value_to_string ~minify:false
  |> A.equals_string __LOC__ {|{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    {
      "schema": "http://schema.org#",
      "PropertyValue": "schema:PropertyValue",
      "value": "schema:value",
      "@language": "und"
    }
  ],
  "type": "OrderedCollectionPage",
  "id": "https://example.com/ap/followers/2.jsa",
  "current": "https://example.com/ap/followers/2.jsa",
  "last": "https://example.com/ap/followers/0.jsa",
  "next": "https://example.com/ap/followers/1.jsa",
  "partOf": "https://example.com/ap/followers/index.jsa",
  "prev": "https://example.com/ap/followers/3.jsa",
  "orderedItems": [
    "https://example.com/u/1/",
    "https://example.com/u/2/",
    "https://example.com/u/3/",
    "https://example.com/u/4/",
    "https://example.com/u/5/"
  ]
}|}

let test_followers_page_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json");
  let foo = "https://example.com/foo/usr/" |> Uri.of_string in
  let base = "https://example.com/" |> Uri.of_string in
  let path = Ap.apub ^ "followers/" in
  let base = Http.reso ~base (Uri.make ~path ()) in
  [ "1/"; "2/"; "3/"; "4/"; "5/" ]
  |> List.rev
  |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
  |> Ap.Followers.Json.to_page_json ~base path ~finish:false 2
  |> Ezjsonm.value_to_string ~minify:false
  |> A.equals_string __LOC__ {|{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://w3id.org/security/v1",
    {
      "schema": "http://schema.org#",
      "PropertyValue": "schema:PropertyValue",
      "value": "schema:value",
      "@language": "und"
    }
  ],
  "type": "OrderedCollectionPage",
  "id": "https://example.com/activitypub/followers/2.jsa",
  "current": "https://example.com/activitypub/followers/2.jsa",
  "last": "https://example.com/activitypub/followers/0.jsa",
  "next": "https://example.com/activitypub/followers/1.jsa",
  "partOf": "https://example.com/activitypub/followers/index.jsa",
  "prev": "https://example.com/activitypub/followers/3.jsa",
  "orderedItems": [
    "https://example.com/foo/usr/1/",
    "https://example.com/foo/usr/2/",
    "https://example.com/foo/usr/3/",
    "https://example.com/foo/usr/4/",
    "https://example.com/foo/usr/5/"
  ]
}|}

let test_followers_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_json");
  let foo = "https://example.com/foo/usr/" |> Uri.of_string in
  let lst = [ "1/"; "2/"; "3/"; "4/"; "5/" ]
            |> List.rev
            |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
  in
  let base = "https://example.com/" |> Uri.of_string in
  let path = Ap.apub ^ "followers/" in
  let base = Http.reso ~base (Uri.make ~path ()) in
  let _ = File.mkdir_p File.pDir path in
  (* let oc = stdout in *)
  File.out_channel ~tmp:None "/dev/null" (fun oc ->
      Ap.Followers.Json.(List.fold_left
                           (fold2pages 3 (flush_page ~base ~oc path))
                           (0,0,[],0)
                           lst
                         |> flush_page ~base ~finish:true ~oc path);
      (* check existence of index.jsa *)
      (* check existence of 0.jsa *)
      (* check existence of 1.jsa *)
      "" |> A.equals_string __LOC__ ""
    )

let test_followers_cdb_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_cdb_json");
  File.out_channel ~tmp:None "/dev/null" (fun oc ->
      let base = "https://example.com/" |> Uri.of_string in
      let path = Ap.apub ^ "follower2/" in
      let base = Http.reso ~base (Uri.make ~path ()) in
      let path = "tmp/" ^ path in
      let _ = File.mkdir_p File.pDir path in
      let _ = Ap.Followers.Json.coll_of_cdb ~base ~oc ~pagesize:3 path (Mapcdb.Cdb "data/followers.cdb") in
      "" |> A.equals_string __LOC__ ""
    )

let test_create_note_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_create_note_json");
  File.in_channel "data/create.note.mastodon2.json" (fun ic ->
      let c : As2_vocab.Types.note As2_vocab.Types.create = Ezjsonm.from_channel ic
                                                            |> As2_vocab.Decode.(create note)
                                                            |> Result.get_ok in
      assert (not c.direct_message);
      let n = c.obj in
      n.id |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro/statuses/111561416759041219";
      n.actor |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro";
      n.published |> Option.get |> Ptime.to_rfc3339 |> A.equals_string __LOC__ "2023-12-11T10:55:25-00:00";
      n.in_reply_to |> List.length |> A.equals_int __LOC__ 0;
      assert (n.summary |> Option.is_none);
      n.content |> A.equals_string __LOC__ {|<p>Good morning 😀  <a href="https://bewegung.social/tags/Social" class="mention hashtag" rel="tag">#<span>Social</span></a> <a href="https://bewegung.social/tags/Web" class="mention hashtag" rel="tag">#<span>Web</span></a>! <span class="h-card" translate="no"><a href="https://seppo.social/demo/" class="u-url mention">@<span>demo</span></a></span></p>|};
      n.tags |> List.length |> A.equals_int __LOC__ 3;
      (match n.tags with
       | [
         {ty=`Mention;name=n0;href=h0};
         {ty=`Hashtag;name=n1;href=h1};
         {ty=`Hashtag;name=n2;href=h2};
       ] ->
         n0 |> A.equals_string __LOC__ "@demo@seppo.social";
         h0 |> Uri.to_string |> A.equals_string __LOC__ "https://seppo.social/demo/activitypub/profile.jlda";
         n1 |> A.equals_string __LOC__ "#social";
         h1 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/social";
         n2 |> A.equals_string __LOC__ "#web";
         h2 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/web";
         ()
       | _ -> failwith "aua"
       (**) );
      assert true
    )

let test_reject_json () =
  Logr.info (fun m -> m "%s.%s" "Ap_test" "test_reject_json");
  let reject me id = 
    `O [("@context", `String "https://www.w3.org/ns/activitystreams");
        ("type", `String "Reject");
        ("actor", `String (me |> Uri.to_string));
        ("object", `String (id |> Uri.to_string))]
  in
  let me = "https://example.com/alice" |> Uri.of_string in
  let id = match File.in_channel "data/create.note.mastodon2.json" Ezjsonm.from_channel with
    | `O (_ :: ("id", `String id) :: _) -> id |> Uri.of_string
    | _ ->  Uri.empty in
  id |> Uri.to_string |> A.equals_string __LOC__ {|https://bewegung.social/users/mro/statuses/111561416759041219/activity|};
  id
  |> reject me
  |> Ezjsonm.value_to_string
  |> A.equals_string __LOC__ {|{"@context":"https://www.w3.org/ns/activitystreams","type":"Reject","actor":"https://example.com/alice","object":"https://bewegung.social/users/mro/statuses/111561416759041219/activity"}|};
  assert true

let test_subscribed () =
  for i = 0 to 10 do 
    try i |> Printf.sprintf "/tmp/%d.xml" |> Unix.unlink
    with | Unix.Unix_error(Unix.ENOENT, "unlink", _) -> ()
  done ;
  let cdb = Mapcdb.Cdb "data/2024-04-30-131146-subscribed.cdb" in
  cdb
  |> Mapcdb.fold_left (fun c _ -> 1 + c) 0 
  |> A.equals_int __LOC__ 52;
  let r = Ap.Followers.Atom.rule in
  r.target |> A.equals_string __LOC__ "activitypub/notify/index.xml";
  (match Ap.Followers.Atom.of_cdb
           ~cdb
           ~title:"My Title"
           ~xsl:(Rfc4287.xsl "my.xsl" "./tmp/noop")
           ~rel:(Some Rfc4287.Link.notify)
           ~page_size:5
           "./tmp/" with
  | Error _ -> failwith __LOC__
  | Ok s -> s)
  |> A.equals_string __LOC__ "./tmp/10.xml";
  "./tmp/0.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>My Title</title>
  <id>0.xml</id>
  <link rel="self" href="0.xml" title="1"/>
  <link rel="first" href="." title="last"/>
  <link rel="last" href="0.xml" title="1"/>
  <link rel="previous" href="1.xml" title="2"/>
  <link rel="notify" href="https://social.wohlfarth.name/users/alain" title=""/>
  <link rel="notify" href="https://hci.social/users/sigchi" title=""/>
  <link rel="notify" href="https://fosstodon.org/users/nlnetlabs" title=""/>
  <link rel="notify" href="https://mastodon.xyz/users/NGIZero" title=""/>
  <link rel="notify" href="https://w3c.social/users/w3c" title=""/>
</feed>|};
  "./tmp/10.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>My Title</title>
  <id>10.xml</id>
  <link rel="self" href="10.xml" title="11"/>
  <link rel="first" href="." title="last"/>
  <link rel="last" href="0.xml" title="1"/>
  <link rel="previous" href="11.xml" title="12"/>
  <link rel="next" href="9.xml" title="10"/>
  <link rel="notify" href="https://mas.to/users/leostera" title=""/>
  <link rel="notify" href="https://social.network.europa.eu/users/EU_Commission" title="European Commission" rfc7033="acct:EU_Commission@social.network.europa.eu"/>
</feed>|};
  "?" |> A.equals_string __LOC__ "?"

let () =
  Unix.chdir "../../../test/";
  test_follower_state ();
  test_inbox_follow ();
  test_inbox_unfollow ();
  test_followers_page_json_raw ();
  test_followers_page_json ();
  test_followers_json ();
  test_followers_cdb_json ();
  test_create_note_json ();
  test_reject_json ();
  test_subscribed ();
  assert true
