require_relative './token'
require_relative './provisioning/provisioning'
require_relative './testflight/testflight'
require_relative './tunes/tunes'
require_relative './users/users'

module Spaceship
  class ConnectAPI
    class Client
      attr_accessor :token
      attr_accessor :tunes_client
      attr_accessor :portal_client

      # Initializes client with Apple's App Store Connect JWT auth key.
      #
      # This method will automatically use the arguments from environment
      # variables if not given.
      #
      # The key_id, issuer_id and either filepath or key are needed to authenticate.
      #
      # @param key_id (String) (optional): The key id
      # @param issuer_id (String) (optional): The issuer id
      # @param filepath (String) (optional): The filepath
      # @param key (String) (optional): The key
      # @param duration (Integer) (optional): How long this session should last
      # @param in_house (Boolean) (optional): Whether this session is an Enterprise one
      #
      # @raise InvalidUserCredentialsError: raised if authentication failed
      #
      # @return (Spaceship::ConnectAPI::Client) The client the login method was called for
      def self.auth(key_id: nil, issuer_id: nil, filepath: nil, key: nil, duration: nil, in_house: nil)
        token = Spaceship::ConnectAPI::Token.create(key_id: key_id, issuer_id: issuer_id, filepath: filepath, key: key, duration: duration, in_house: in_house)
        return ConnectAPI::Client.new(token: token)
      end

      # Authenticates with Apple's web services. This method has to be called once
      # to generate a valid session.
      #
      # This method will automatically use the username from the Appfile (if available)
      # and fetch the password from the Keychain (if available)
      #
      # @param user (String) (optional): The username (usually the email address)
      # @param password (String) (optional): The password
      # @param use_portal (Boolean) (optional): Whether to log in to Spaceship::Portal or not
      # @param use_tunes (Boolean) (optional): Whether to log in to Spaceship::Tunes or not
      # @param portal_team_id (String) (optional): The Spaceship::Portal team id
      # @param tunes_team_id (String) (optional): The Spaceship::Tunes team id
      # @param team_name (String) (optional): The team name
      # @param skip_select_team (Boolean) (optional): Whether to skip automatic selection or prompt for team
      #
      # @raise InvalidUserCredentialsError: raised if authentication failed
      #
      # @return (Spaceship::ConnectAPI::Client) The client the login method was called for
      def self.login(user = nil, password = nil, use_portal: true, use_tunes: true, portal_team_id: nil, tunes_team_id: nil, team_name: nil, skip_select_team: false)
        portal_client = Spaceship::Portal.login(user, password) if use_portal
        tunes_client = Spaceship::Tunes.login(user, password) if use_tunes

        unless skip_select_team
          # Check if environment variables are set for Spaceship::Portal or Spaceship::Tunes to select team
          portal_team_id ||= ENV['FASTLANE_TEAM_ID']
          portal_team_name = team_name || ENV['FASTLANE_TEAM_NAME']
          tunes_team_id ||= ENV['FASTLANE_ITC_TEAM_ID']
          tunes_team_name = team_name || ENV['FASTLANE_ITC_TEAM_NAME']

          # The clients will prompt for a team selection if:
          # 1. client exists
          # 2. team_id and team_name are nil and user belongs to multiple teams
          portal_client.select_team(team_id: portal_team_id, team_name: portal_team_name) if portal_client
          tunes_client.select_team(team_id: tunes_team_id, team_name: tunes_team_name) if tunes_client
        end

        return ConnectAPI::Client.new(tunes_client: tunes_client, portal_client: portal_client)
      end

      def initialize(cookie: nil, current_team_id: nil, token: nil, tunes_client: nil, portal_client: nil)
        @token = token

        # If using web session...
        # Spaceship::Tunes is needed for TestFlight::API, Tunes::API, and Users::API
        # Spaceship::Portal is needed for Provisioning::API
        @tunes_client = tunes_client
        @portal_client = portal_client

        # Extending this instance to add API endpoints from these modules
        # Each of these modules adds a new setter method for an instance
        # of an ConnectAPI::APIClient
        # These get set in set_individual_clients
        self.extend(Spaceship::ConnectAPI::TestFlight::API)
        self.extend(Spaceship::ConnectAPI::Tunes::API)
        self.extend(Spaceship::ConnectAPI::Provisioning::API)
        self.extend(Spaceship::ConnectAPI::Users::API)

        set_individual_clients(
          cookie: cookie,
          current_team_id: current_team_id,
          token: token,
          tunes_client: @tunes_client,
          portal_client: @portal_client
        )
      end

      def portal_team_id
        if token
          message = [
            "Cannot determine portal team id via the App Store Connect API (yet)",
            "Look to see if you can get the portal team id from somewhere else",
            "View more info in the docs at https://docs.fastlane.tools/app-store-connect-api/"
          ]
          raise message.join('. ')
        elsif @portal_client
          return @portal_client.team_id
        else
          raise "No App Store Connect API token or Portal Client set"
        end
      end

      def tunes_team_id
        return nil if @tunes_client.nil?
        return @tunes_client.team_id
      end

      def portal_teams
        return nil if @portal_client.nil?
        return @portal_client.teams
      end

      def tunes_teams
        return nil if @tunes_client.nil?
        return @tunes_client.teams
      end

      def in_house?
        if token
          if token.in_house.nil?
            message = [
              "Cannot determine if team is App Store or Enterprise via the App Store Connect API (yet)",
              "Set 'in_house' on your Spaceship::ConnectAPI::Token",
              "Or set 'in_house' in your App Store Connect API key JSON file",
              "Or set the 'SPACESHIP_CONNECT_API_IN_HOUSE' environment variable to 'true'",
              "View more info in the docs at https://docs.fastlane.tools/app-store-connect-api/"
            ]
            raise message.join('. ')
          end
          return !!token.in_house
        elsif @portal_client
          return @portal_client.in_house?
        else
          raise "No App Store Connect API token or Portal Client set"
        end
      end

      def select_team(portal_team_id: nil, tunes_team_id: nil, team_name: nil)
        @portal_client.select_team(team_id: portal_team_id, team_name: team_name) unless @portal_client.nil?
        @tunes_client.select_team(team_id: tunes_team_id, team_name: team_name) unless @tunes_client.nil?

        # Updating the tunes and portal clients requires resetting
        # of the clients in the API modules
        set_individual_clients(
          cookie: nil,
          current_team_id: nil,
          token: nil,
          tunes_client: tunes_client,
          portal_client: portal_client
        )
      end

      private

      def set_individual_clients(cookie: nil, current_team_id: nil, token: nil, tunes_client: nil, portal_client: nil)
        # This was added by Spaceship::ConnectAPI::TestFlight::API and is required
        # to be set for API methods to have a client to send request on
        if cookie || token || tunes_client
          self.test_flight_request_client = Spaceship::ConnectAPI::TestFlight::Client.new(
            cookie: cookie,
            current_team_id: current_team_id,
            token: token,
            another_client: tunes_client
          )
        end

        # This was added by Spaceship::ConnectAPI::Tunes::API and is required
        # to be set for API methods to have a client to send request on
        if cookie || token || tunes_client
          self.tunes_request_client = Spaceship::ConnectAPI::Tunes::Client.new(
            cookie: cookie,
            current_team_id: current_team_id,
            token: token,
            another_client: tunes_client
          )
        end

        # This was added by Spaceship::ConnectAPI::Provisioning::API and is required
        # to be set for API methods to have a client to send request on
        if cookie || token || portal_client
          self.provisioning_request_client = Spaceship::ConnectAPI::Provisioning::Client.new(
            cookie: cookie,
            current_team_id: current_team_id,
            token: token,
            another_client: portal_client
          )
        end

        # This was added by Spaceship::ConnectAPI::Users::API and is required
        # to be set for API methods to have a client to send request on
        if cookie || token || tunes_client
          self.users_request_client = Spaceship::ConnectAPI::Users::Client.new(
            cookie: cookie,
            current_team_id: current_team_id,
            token: token,
            another_client: tunes_client
          )
        end
      end
    end
  end
end
