#import "CodePushDeployReporter.h"
#import "CodePush.h"

// Wire-key constants — must match the snake_case keys produced by the
// JS-side CodePushApiSdk.reportStatusDeploy so the server sees a uniform
// payload regardless of which path (native vs JS) emitted it.
static NSString *const kPathReportDeploy            = @"/report_status/deploy";
static NSString *const kKeyDeploymentKey            = @"deployment_key";
static NSString *const kKeyAppVersion               = @"app_version";
static NSString *const kKeyClientUniqueId           = @"client_unique_id";
static NSString *const kKeyStatus                   = @"status";
static NSString *const kKeyLabel                    = @"label";
static NSString *const kKeyErrorCode                = @"error_code";
static NSString *const kKeyErrorMessage             = @"error_message";
static NSString *const kKeyFailedPhase              = @"failed_phase";
static NSString *const kKeyBinaryAppVersion         = @"binary_app_version";

// Package-side keys (mirrors the dictionary returned by CodePushPackage)
static NSString *const kPackageKeyLabel             = @"label";
static NSString *const kPackageKeyAppVersion        = @"appVersion";

static NSString *const kStatusDeploymentFailed      = @"DeploymentFailed";

// Wire-level length caps (kept identical to the JS SDK so server validation
// never trips on the native path).
static const NSUInteger kErrorCodeMaxLen      = 64;
static const NSUInteger kErrorMessageMaxLen   = 500;
static const NSUInteger kFailedPhaseMaxLen    = 20;
static const NSUInteger kBinaryAppVersionMaxLen = 40;

@implementation CodePushDeployReporter

+ (void)reportFailureWithPackage:(NSDictionary *)failedPackage
                       errorCode:(NSString *)errorCode
                    errorMessage:(NSString *)errorMessage
                     failedPhase:(NSString *)failedPhase
{
    CodePushConfig *config = [CodePushConfig current];

    // Match the JS-side guard: bail out when either telemetry or data
    // transmission is disabled. This keeps the on/off semantics identical
    // across the two upload paths.
    if (!config.dataTransmissionEnabled || !config.telemetryEnabled) {
        return;
    }

    NSString *serverURL = config.serverURL;
    NSString *deploymentKey = config.releaseChannelPublicId;
    NSString *clientUniqueId = config.clientUniqueId;
    NSString *binaryAppVersion = config.appVersion;

    // We can't post anything useful without these three. Silently skip and
    // let the next-sync fallback (saveFailedUpdate + needToReportRollback)
    // handle it.
    if (serverURL.length == 0 || deploymentKey.length == 0 || clientUniqueId.length == 0) {
        return;
    }

    NSString *normalizedErrorCode = [self truncate:[self stringOrNil:errorCode] toLength:kErrorCodeMaxLen];
    if (normalizedErrorCode.length == 0) {
        normalizedErrorCode = @"UNKNOWN";
    }
    NSString *normalizedFailedPhase = [self normalizeFailedPhase:[self stringOrNil:failedPhase]];
    NSString *normalizedErrorMessage = [self truncate:[self stringOrNil:errorMessage] toLength:kErrorMessageMaxLen];
    NSString *normalizedBinaryAppVersion = [self truncate:[self stringOrNil:binaryAppVersion] toLength:kBinaryAppVersionMaxLen];

    NSMutableDictionary *body = [NSMutableDictionary dictionary];
    body[kKeyDeploymentKey] = deploymentKey;
    body[kKeyClientUniqueId] = clientUniqueId;
    body[kKeyStatus] = kStatusDeploymentFailed;

    // `app_version` mirrors the JS contract: defaults to the client binary
    // version, but gets overwritten to the failed package's `appVersion`
    // (target_binary_range) when a package is supplied.
    if (binaryAppVersion.length > 0) {
        body[kKeyAppVersion] = binaryAppVersion;
    }
    if ([failedPackage isKindOfClass:[NSDictionary class]]) {
        NSString *pkgLabel = [self stringOrNil:failedPackage[kPackageKeyLabel]];
        NSString *pkgAppVersion = [self stringOrNil:failedPackage[kPackageKeyAppVersion]];
        if (pkgLabel.length > 0) {
            body[kKeyLabel] = pkgLabel;
        }
        if (pkgAppVersion.length > 0) {
            body[kKeyAppVersion] = pkgAppVersion;
        }
    }

    body[kKeyErrorCode] = normalizedErrorCode;
    body[kKeyFailedPhase] = normalizedFailedPhase;
    if (normalizedErrorMessage.length > 0) {
        body[kKeyErrorMessage] = normalizedErrorMessage;
    }
    if (normalizedBinaryAppVersion.length > 0) {
        body[kKeyBinaryAppVersion] = normalizedBinaryAppVersion;
    }

    NSError *jsonError = nil;
    NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body options:0 error:&jsonError];
    if (jsonError || bodyData == nil) {
        CPLog(@"[DeployReporter] JSON serialization failed: %@", jsonError);
        return;
    }

    NSString *urlString = [serverURL stringByAppendingString:kPathReportDeploy];
    NSURL *url = [NSURL URLWithString:urlString];
    if (url == nil) {
        CPLog(@"[DeployReporter] invalid URL: %@", urlString);
        return;
    }

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    request.HTTPBody = bodyData;
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    // Short timeout — we're fire-and-forget, no point hanging the network
    // queue on a slow report. Failures are tolerated; the next-sync fallback
    // re-sends from FailedUpdates anyway.
    request.timeoutInterval = 10.0;

    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request
                                                                 completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *error) {
        if (error) {
            CPLog(@"[DeployReporter] HTTP error: %@", error.localizedDescription);
            return;
        }
        NSInteger statusCode = 0;
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            statusCode = [(NSHTTPURLResponse *)response statusCode];
        }
        if (statusCode < 200 || statusCode >= 300) {
            CPLog(@"[DeployReporter] HTTP %ld for %@", (long)statusCode, urlString);
        } else {
            CPLog(@"[DeployReporter] reported (errorCode=%@, failedPhase=%@)", normalizedErrorCode, normalizedFailedPhase);
        }
    }];
    [task resume];
}

#pragma mark - helpers

+ (NSString *)stringOrNil:(id)value
{
    if ([value isKindOfClass:[NSString class]]) {
        return (NSString *)value;
    }
    return nil;
}

+ (NSString *)truncate:(NSString *)value toLength:(NSUInteger)maxLen
{
    if (value == nil) return nil;
    if (value.length == 0) return nil;
    if (value.length <= maxLen) return value;
    return [value substringToIndex:maxLen];
}

+ (NSString *)normalizeFailedPhase:(NSString *)phase
{
    if ([@"download" isEqualToString:phase]
        || [@"install"  isEqualToString:phase]
        || [@"runtime"  isEqualToString:phase]) {
        return phase;
    }
    return @"unknown";
}

@end
