import { Injectable } from "@angular/core";
import { TwitterApiProvider } from "../twitter-api/twitter-api";
import { P2pDatabaseGunProvider } from "../p2p-database-gun/p2p-database-gun";
import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs";

@Injectable()
export class FeedProvider {
  friends;

  constructor(
    private twitter: TwitterApiProvider,
    private gun: P2pDatabaseGunProvider,
    private ipfs: P2pStorageIpfsProvider
  ) {}

  public async loadUserTimeline(
    userId,
    oldestPublicTweet?,
    oldestPrivateTweet?
  ) {
    const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
    // Fetch tweets from Twitter
    let tweets = await this.twitter.fetchUserTimeline(userId, maxId);
    tweets = tweets.filter(tweet => tweet.id_str != maxId);

    // Determine start and end of time interval to look for private tweets
    const intervalStart: Date = oldestPrivateTweet
      ? new Date(oldestPrivateTweet["created_at"])
      : new Date();
    const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);

    // Fetch private tweet hashs from P2P DB for corresponding interval
    const privateTweetHashs: string[] = await this.gun.fetchPrivateTweetHashsForUserInInterval(
      userId,
      intervalStart,
      intervalEnd
    );

    if (privateTweetHashs.length) {
      const privateTweets = await this.fetchPrivateTweets(privateTweetHashs);

      // Combine and sort tweets
      return tweets
        .concat(privateTweets)
        .sort((a, b) => this.sortByDateAsc(a, b));
    } else {
      return tweets;
    }
  }

  public async loadHomeTimeline(
    userId,
    oldestPublicTweet?,
    oldestPrivateTweet?
  ) {
    // Fetch tweets from Twitter
    const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
    let tweets = await this.twitter.fetchHomeFeed(maxId);
    tweets = tweets.filter(tweet => tweet.id_str != maxId);

    // Determine start and end of time interval to look for private tweets
    const intervalStart: Date = oldestPrivateTweet
      ? new Date(oldestPrivateTweet["created_at"])
      : new Date();
    const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);

    // Fetch user's friends
    const friends = await this.getCachedFriends(userId);

    let privateTweetHashs = [];
    friends.forEach(async friend => {
      privateTweetHashs = privateTweetHashs.concat(
        await this.gun.fetchPrivateTweetHashsForUserInInterval(
          friend.id_str,
          intervalStart,
          intervalEnd
        )
      );
    });

    // Add users private tweets
    privateTweetHashs = privateTweetHashs.concat(
      await this.gun.fetchPrivateTweetHashsForUserInInterval(
        userId,
        intervalStart,
        intervalEnd
      )
    );

    if (privateTweetHashs.length) {
      const privateTweets = await this.fetchPrivateTweets(privateTweetHashs);

      // Combine and sort tweets
      return tweets
        .concat(privateTweets)
        .sort((a, b) => this.sortByDateAsc(a, b));
    } else {
      return tweets;
    }
  }

  private async fetchPrivateTweets(privateTweetHashs) {
    // Load private tweets from P2P storage
    const privateTweets = await this.ipfs.fetchTweets(privateTweetHashs);

    // Add user object to private tweets
    return await Promise.all(
      privateTweets.map(async tweet => await this.addUserToTweet(tweet))
    );
  }

  private async addUserToTweet(tweet) {
    tweet.user = await this.twitter.fetchUser(tweet.user_id);
    return tweet;
  }

  private getOldestTweetTimestamp(tweets): Date {
    if (tweets.length < 15) {
      // End of timeline is reached - load all private tweets
      return new Date("2018-04-01T00:00:00");
    } else {
      const lastTweetTimestamp = tweets[tweets.length - 1].created_at;
      return new Date(lastTweetTimestamp);
    }
  }

  private sortByDateAsc(a, b) {
    const dateA = new Date(a.created_at);
    const dateB = new Date(b.created_at);

    if (dateA > dateB) {
      return -1;
    } else if (dateA < dateB) {
      return 1;
    } else {
      return 0;
    }
  }

  private async getCachedFriends(userId) {
    // Cache friends for 15 minutes to avoid unnecessary  API calls
    if (!this.friends || (Date.now() - this.friends.lastUpdate) / 900000 > 15) {
      this.friends = {
        friendList: await this.twitter.fetchFriends(userId),
        lastUpdate: Date.now()
      };
    }
    return this.friends.friendList;
  }
}
