// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/updater/policy/mac/managed_preference_policy_manager_impl.h"

#include <Foundation/Foundation.h>

#include "base/apple/foundation_util.h"
#include "base/enterprise_util.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/policy/manager.h"

namespace {
// Constants for managed preference policy keys.
NSString* const kGlobalPolicyKey = @"global";
NSString* const kUpdateDefaultKey = @"UpdateDefault";
NSString* const kDownloadPreferenceKey = @"DownloadPreference";
NSString* const kUpdatesSuppressedStartHourKey = @"UpdatesSuppressedStartHour";
NSString* const kUpdatesSuppressedStartMinuteKey = @"UpdatesSuppressedStartMin";
NSString* const kUpdatesSuppressedDurationMinuteKey =
    @"UpdatesSuppressedDurationMin";
NSString* const kTargetChannelKey = @"TargetChannel";
NSString* const kTargetVersionPrefixKey = @"TargetVersionPrefix";
NSString* const kRollbackToTargetVersionKey = @"RollbackToTargetVersion";
NSString* const kMajorVersionRolloutKey = @"MajorVersionRolloutPolicy";
NSString* const kMinorVersionRolloutKey = @"MinorVersionRolloutPolicy";
}  // namespace

namespace updater {

// Extracts an integer value from a NSString or NSNumber. Returns kPolicyNotSet
// for all unexpected cases.
int ReadPolicyInteger(id value) {
  if (!value) {
    return kPolicyNotSet;
  }

  NSInteger result = kPolicyNotSet;
  if ([value isKindOfClass:[NSString class]]) {
    NSScanner* scanner = [NSScanner scannerWithString:(NSString*)value];
    if (![scanner scanInteger:&result]) {
      return kPolicyNotSet;
    }
  } else if ([value isKindOfClass:[NSNumber class]]) {
    result = [(NSNumber*)value intValue];
  }

  return static_cast<int>(result);
}

// For historical reasons, "update" policy has different enum values in Manage
// Preferences from the Device Management. This function converts the former
// to latter.
// +----------------+---------------------+--------------------+
// | Update policy  | Managed Preferences |  Device Management |
// +----------------+---------------------+--------------------+
// | Enabled        |          0          |         1          |
// +----------------+---------------------+--------------------+
// | Automatic only |          1          |         3          |
// +----------------+---------------------+--------------------+
// | Manual only    |          2          |         2          |
// +----------------+---------------------+--------------------+
// | Disabled       |          3          |         0          |
// +----------------+---------------------+--------------------+
// | Machine only   |          4          |         4          |
// +----------------+---------------------+--------------------+
int TranslateUpdatePolicyValue(int update_policy_from_managed_preferences) {
  switch (update_policy_from_managed_preferences) {
    case 0:
      return kPolicyEnabled;
    case 1:
      return kPolicyAutomaticUpdatesOnly;
    case 2:
      return kPolicyManualUpdatesOnly;
    case 3:
      return kPolicyDisabled;
    case 4:
      return kPolicyEnabledMachineOnly;
    default:
      return kPolicyNotSet;
  }
}

}  // namespace updater

/// Class that manages Mac global-level policies.
@interface CRUManagedPreferenceGlobalPolicySettings : NSObject {
  NSString* __strong _downloadPreference;
};

@property(nonatomic, readonly) int lastCheckPeriodMinutes;
@property(nonatomic, readonly) int defaultUpdatePolicy;
@property(nonatomic, readonly, nullable) NSString* downloadPreference;
@property(nonatomic, readonly, nullable) NSString* proxyMode;
@property(nonatomic, readonly, nullable) NSString* proxyServer;
@property(nonatomic, readonly, nullable) NSString* proxyPacURL;
@property(nonatomic, readonly)
    updater::UpdatesSuppressedTimes updatesSuppressed;

@end

@implementation CRUManagedPreferenceGlobalPolicySettings

@synthesize defaultUpdatePolicy = _defaultUpdatePolicy;
@synthesize updatesSuppressed = _updatesSuppressed;

- (instancetype)initWithDictionary:(CRUAppPolicyDictionary*)policyDict {
  if (([super init])) {
    _downloadPreference =
        base::apple::ObjCCast<NSString>(policyDict[kDownloadPreferenceKey]);
    _defaultUpdatePolicy = updater::TranslateUpdatePolicyValue(
        updater::ReadPolicyInteger(policyDict[kUpdateDefaultKey]));
    _updatesSuppressed.start_hour_ =
        updater::ReadPolicyInteger(policyDict[kUpdatesSuppressedStartHourKey]);
    _updatesSuppressed.start_minute_ = updater::ReadPolicyInteger(
        policyDict[kUpdatesSuppressedStartMinuteKey]);
    _updatesSuppressed.duration_minute_ = updater::ReadPolicyInteger(
        policyDict[kUpdatesSuppressedDurationMinuteKey]);
  }
  return self;
}

- (int)lastCheckPeriodMinutes {
  // LastCheckPeriodMinutes is not supported in Managed Preference policy.
  return updater::kPolicyNotSet;
}

- (NSString*)downloadPreference {
  if (_downloadPreference) {
    return _downloadPreference;
  } else {
    return nil;
  }
}

- (NSString*)proxyMode {
  return nil;  //  ProxyMode is not supported in Managed Preference policy.
}

- (NSString*)proxyServer {
  return nil;  // ProxyServer is not supported in Managed Preference policy.
}

- (NSString*)proxyPacURL {
  return nil;  //  ProxyPacURL is not supported in Managed Preference policy.
}

@end

/// Class that manages policies for a single App.
@interface CRUManagedPreferenceAppPolicySettings : NSObject {
  NSString* __strong _targetChannel;
  NSString* __strong _targetVersionPrefix;
}

@property(nonatomic, readonly) int updatePolicy;
@property(nonatomic, readonly) int rollbackToTargetVersion;
@property(nonatomic, readonly, nullable) NSString* targetChannel;
@property(nonatomic, readonly, nullable) NSString* targetVersionPrefix;
@property(nonatomic, readonly) int majorVersionRolloutPolicy;
@property(nonatomic, readonly) int minorVersionRolloutPolicy;

@end

@implementation CRUManagedPreferenceAppPolicySettings

@synthesize updatePolicy = _updatePolicy;
@synthesize rollbackToTargetVersion = _rollbackToTargetVersion;
@synthesize majorVersionRolloutPolicy = _majorVersionRolloutPolicy;
@synthesize minorVersionRolloutPolicy = _minorVersionRolloutPolicy;

- (instancetype)initWithDictionary:(CRUAppPolicyDictionary*)policyDict {
  if (([super init])) {
    _updatePolicy = updater::TranslateUpdatePolicyValue(
        updater::ReadPolicyInteger(policyDict[kUpdateDefaultKey]));
    _targetChannel =
        base::apple::ObjCCast<NSString>(policyDict[kTargetChannelKey]);
    _targetVersionPrefix =
        base::apple::ObjCCast<NSString>(policyDict[kTargetVersionPrefixKey]);
    _rollbackToTargetVersion =
        updater::ReadPolicyInteger(policyDict[kRollbackToTargetVersionKey]);
    _majorVersionRolloutPolicy =
        updater::ReadPolicyInteger(policyDict[kMajorVersionRolloutKey]);
    _minorVersionRolloutPolicy =
        updater::ReadPolicyInteger(policyDict[kMinorVersionRolloutKey]);
  }

  return self;
}

- (NSString*)targetChannel {
  if (_targetChannel) {
    return [NSString stringWithString:_targetChannel];
  } else {
    return nil;
  }
}

- (NSString*)targetVersionPrefix {
  if (_targetVersionPrefix) {
    return [NSString stringWithString:_targetVersionPrefix];
  } else {
    return nil;
  }
}

@end

@implementation CRUManagedPreferencePolicyManager {
  CRUManagedPreferenceGlobalPolicySettings* __strong _globalPolicy;
  NSMutableDictionary<NSString*, CRUManagedPreferenceAppPolicySettings*>*
      __strong _appPolicies;
}

@synthesize hasActivePolicy = _hasActivePolicy;

- (instancetype)initWithDictionary:(CRUUpdatePolicyDictionary*)policies {
  if (([super init])) {
    _hasActivePolicy = policies.count > 0;

    // Always create a global policy instance for default values.
    _globalPolicy = [[CRUManagedPreferenceGlobalPolicySettings alloc]
        initWithDictionary:nil];

    _appPolicies = [[NSMutableDictionary alloc] init];
    for (NSString* __strong appid in policies.allKeys) {
      if (![policies[appid] isKindOfClass:[CRUAppPolicyDictionary class]]) {
        continue;
      }

      CRUAppPolicyDictionary* policyDict = policies[appid];
      appid = appid.lowercaseString;
      if ([appid isEqualToString:kGlobalPolicyKey]) {
        _globalPolicy = [[CRUManagedPreferenceGlobalPolicySettings alloc]
            initWithDictionary:policyDict];
      } else {
        CRUManagedPreferenceAppPolicySettings* appSettings =
            [[CRUManagedPreferenceAppPolicySettings alloc]
                initWithDictionary:policyDict];
        [_appPolicies setObject:appSettings forKey:appid];
      }
    }
  }
  return self;
}

- (NSString*)source {
  return [NSString stringWithUTF8String:updater::kSourcePlatformPolicyManager];
}

- (NSString*)downloadPreference {
  return [_globalPolicy downloadPreference];
}

- (NSString*)proxyMode {
  return [_globalPolicy proxyMode];
}

- (NSString*)proxyServer {
  return [_globalPolicy proxyServer];
}

- (NSString*)proxyPacURL {
  return [_globalPolicy proxyPacURL];
}

- (int)lastCheckPeriodMinutes {
  return [_globalPolicy lastCheckPeriodMinutes];
}

- (int)defaultUpdatePolicy {
  return [_globalPolicy defaultUpdatePolicy];
}

- (updater::UpdatesSuppressedTimes)updatesSuppressed {
  return [_globalPolicy updatesSuppressed];
}

- (int)appUpdatePolicy:(NSString*)appid {
  appid = appid.lowercaseString;
  if (![_appPolicies objectForKey:appid]) {
    return updater::kPolicyNotSet;
  }
  return [_appPolicies objectForKey:appid].updatePolicy;
}

- (NSString*)targetChannel:(NSString*)appid {
  appid = appid.lowercaseString;
  return [_appPolicies objectForKey:appid].targetChannel;
}

- (NSString*)targetVersionPrefix:(NSString*)appid {
  appid = appid.lowercaseString;
  return [_appPolicies objectForKey:appid].targetVersionPrefix;
}

- (int)rollbackToTargetVersion:(NSString*)appid {
  appid = appid.lowercaseString;
  if (![_appPolicies objectForKey:appid]) {
    return updater::kPolicyNotSet;
  }
  return [_appPolicies objectForKey:appid].rollbackToTargetVersion;
}

- (int)majorVersionRolloutPolicy:(NSString*)appid {
  appid = appid.lowercaseString;
  if (![_appPolicies objectForKey:appid]) {
    return updater::kPolicyNotSet;
  }
  return [_appPolicies objectForKey:appid].majorVersionRolloutPolicy;
}

- (int)minorVersionRolloutPolicy:(NSString*)appid {
  appid = appid.lowercaseString;
  if (![_appPolicies objectForKey:appid]) {
    return updater::kPolicyNotSet;
  }
  return [_appPolicies objectForKey:appid].minorVersionRolloutPolicy;
}

- (NSArray<NSString*>*)appsWithPolicy {
  return [_appPolicies allKeys];
}

@end
