#import "TQDownloadManager.h"
#import "TQLogger.h"
#import "TQHTTPHeaderCompat.h"

@interface TQDownloadManager ()
@property (nonatomic, copy, nullable) TQOnEventBlock onEvent;
@property (nonatomic, weak, nullable) UIViewController *hostController;
/// 当前下载的原始 URL(用于事件上报和 fallback)
@property (nonatomic, strong, nullable) NSURL *originalURL;
@end

@implementation TQDownloadManager

- (instancetype)initWithOnEvent:(TQOnEventBlock)onEvent
                 hostController:(UIViewController *)hostController {
    if (self = [super init]) {
        _onEvent = [onEvent copy];
        _hostController = hostController;
    }
    return self;
}

- (void)updateHostController:(UIViewController *)hostController {
    self.hostController = hostController;
}

#pragma mark - 统一下载入口

- (void)downloadURL:(NSURL *)url
           response:(NSURLResponse *)response
          dataStore:(WKWebsiteDataStore *)dataStore
downloadWithCookies:(BOOL)downloadWithCookies {
    self.originalURL = url;

    NSString *mime = response.MIMEType ?: @"";
    NSString *disposition = @"";
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
        disposition = [TQHTTPHeaderCompat valueForHeaderField:@"Content-Disposition" inResponse:response] ?: @"";
    }
    if (self.onEvent) {
        self.onEvent([[TQDownloadStartEvent alloc] initWithUrl:url.absoluteString
                                                      mimeType:mime
                                            contentDisposition:disposition]);
    }

    TQLogDownloadInfo("Starting download: %{private}@, withCookies=%d",
                      url.absoluteString, downloadWithCookies);

    if (!downloadWithCookies) {
        // 不携带 Cookie,直接发起下载
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        [self performDownloadWithRequest:request];
        return;
    }

    WKWebsiteDataStore *store = dataStore ?: [WKWebsiteDataStore defaultDataStore];
    __weak __typeof(self) weakSelf = self;
    [store.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> *cookies) {
        __strong __typeof(weakSelf) self = weakSelf;
        if (!self) return;

        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        // 只注入与目标 URL 域名匹配的 Cookie
        NSString *host = url.host;
        NSMutableArray<NSHTTPCookie *> *matched = [NSMutableArray array];
        if (host.length > 0) {
            for (NSHTTPCookie *c in cookies) {
                NSString *domain = c.domain;
                if ([domain hasPrefix:@"."]) domain = [domain substringFromIndex:1];
                if ([host isEqualToString:domain] ||
                    [host hasSuffix:[NSString stringWithFormat:@".%@", domain]]) {
                    [matched addObject:c];
                }
            }
        }
        NSDictionary<NSString *, NSString *> *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:matched];
        for (NSString *key in headers) {
            [request setValue:headers[key] forHTTPHeaderField:key];
        }
        TQLogDownloadDebug("Injected %lu cookies, matched headers: %lu",
                           (unsigned long)cookies.count, (unsigned long)headers.count);

        [self performDownloadWithRequest:request];
    }];
}

#pragma mark - URLSession 下载

- (void)performDownloadWithRequest:(NSURLRequest *)request {
    NSURL *url = request.URL;
    __weak __typeof(self) weakSelf = self;
    NSURLSessionDownloadTask *task = [[NSURLSession sharedSession]
        downloadTaskWithRequest:request
              completionHandler:^(NSURL *tmpURL, NSURLResponse *urlResponse, NSError *error) {
        __strong __typeof(weakSelf) self = weakSelf;
        if (!self) return;

        if (error) {
            TQLogDownloadError("Download failed: %{public}@", error.localizedDescription);
            if (self.onEvent) {
                self.onEvent([[TQDownloadFailedEvent alloc] initWithUrl:url.absoluteString
                                                                  reason:error.localizedDescription]);
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                [self fallbackToSafari:url];
            });
            return;
        }

        if (tmpURL == nil) {
            TQLogDownloadError("Download: tmpURL is nil");
            if (self.onEvent) {
                self.onEvent([[TQDownloadFailedEvent alloc] initWithUrl:url.absoluteString
                                                                  reason:@"Downloaded file URL is nil"]);
            }
            return;
        }

        // 确定文件名（sanitize 防路径穿越：服务器可返回含 ../ 或 / 的恶意名）
        NSString *safeName = [TQDownloadManager sanitizedFilename:(urlResponse.suggestedFilename ?: url.lastPathComponent)];
        NSURL *destDir = [[NSURL fileURLWithPath:NSTemporaryDirectory()]
                          URLByAppendingPathComponent:@"QianDownloads" isDirectory:YES];
        [[NSFileManager defaultManager] createDirectoryAtURL:destDir
                                 withIntermediateDirectories:YES
                                                  attributes:nil
                                                       error:NULL];
        // 落盘二次防御：safeName 必须是「纯文件名」（不含任何路径分隔符，也不是 . / ..）。
        // sanitizedFilename 已剥离 / 与 ../，此处按文件名层面做等值校验即可，
        // 不再用 URLByStandardizingPath 绝对路径 hasPrefix 比较——后者在真机上因
        // /var → /private/var 符号链接对「已存在的 destDir」与「尚不存在的 destURL」
        // 解析不对称，会把合法文件名（如 sample-2-pages.pdf）误判为路径穿越并拒绝下载。
        if (![safeName isEqualToString:safeName.lastPathComponent]
            || [safeName containsString:@"/"]
            || [safeName isEqualToString:@"."]
            || [safeName isEqualToString:@".."]) {
            TQLogDownloadError("Download: illegal filename, rejected");
            if (self.onEvent) {
                self.onEvent([[TQDownloadFailedEvent alloc] initWithUrl:url.absoluteString
                                                                  reason:@"Illegal filename"]);
            }
            return;
        }
        NSURL *destURL = [destDir URLByAppendingPathComponent:safeName];
        [[NSFileManager defaultManager] removeItemAtURL:destURL error:NULL];

        NSError *moveError = nil;
        BOOL ok = [[NSFileManager defaultManager] moveItemAtURL:tmpURL
                                                          toURL:destURL
                                                          error:&moveError];
        if (!ok) {
            TQLogDownloadError("Download: move file failed: %{public}@", moveError.localizedDescription);
            if (self.onEvent) {
                self.onEvent([[TQDownloadFailedEvent alloc] initWithUrl:url.absoluteString
                                                                  reason:moveError.localizedDescription]);
            }
            return;
        }
        TQLogDownloadInfo("Download success: %{private}@", destURL.path);
        if (self.onEvent) {
            self.onEvent([[TQDownloadCompletedEvent alloc] initWithUrl:url.absoluteString
                                                              localPath:destURL.path]);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [self presentShareSheetWithRetry:destURL retryCount:5];
        });
    }];
    [task resume];
}

#pragma mark - 分享 Sheet(带重试)

/// 带重试的分享 sheet 弹出。
/// 当 `location.href` 触发主框架下载时,WKWebView 内部状态转换可能导致
/// VC 短暂不可 present,通过延迟重试解决。
- (void)presentShareSheetWithRetry:(NSURL *)fileURL retryCount:(NSInteger)retryCount {
    UIViewController *vc = self.hostController;
    if (vc == nil) {
        TQLogDownloadWarn("presentShareSheetWithRetry: hostController is nil, fallback");
        [self fallbackToSafari:self.originalURL];
        [self cleanupFile:fileURL];
        return;
    }
    if ([self canPresentOn:vc]) {
        TQLogDownloadDebug("presentShareSheetWithRetry: canPresent=YES, presenting (remaining retries: %ld)",
                           (long)retryCount);
        [self presentShareSheet:fileURL];
        return;
    }
    if (retryCount > 0) {
        TQLogDownloadDebug("presentShareSheetWithRetry: canPresent=NO, retrying in 0.3s (remaining: %ld)",
                           (long)retryCount);
        TQLogDownloadDebug("  isBeingDismissed=%d, isBeingPresented=%d, hasWindow=%d",
                           vc.isBeingDismissed, vc.isBeingPresented, vc.view.window != nil);
        __weak __typeof(self) weakSelf = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)),
                       dispatch_get_main_queue(), ^{
            [weakSelf presentShareSheetWithRetry:fileURL retryCount:retryCount - 1];
        });
        return;
    }
    TQLogDownloadWarn("presentShareSheetWithRetry: retries exhausted, fallback to Safari");
    [self fallbackToSafari:self.originalURL];
    [self cleanupFile:fileURL];
}

- (void)presentShareSheet:(NSURL *)fileURL {
    UIViewController *vc = self.hostController;
    if (vc == nil) return;

    UIActivityViewController *activityVC = [[UIActivityViewController alloc]
                                             initWithActivityItems:@[ fileURL ]
                                             applicationActivities:nil];
    // iPad 需要 popoverPresentationController
    UIPopoverPresentationController *popover = activityVC.popoverPresentationController;
    if (popover) {
        popover.sourceView = vc.view;
        CGRect bounds = vc.view.bounds;
        popover.sourceRect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 0, 0);
        popover.permittedArrowDirections = 0;
    }
    __weak __typeof(self) weakSelf = self;
    activityVC.completionWithItemsHandler = ^(UIActivityType activityType, BOOL completed,
                                              NSArray *returnedItems, NSError *activityError) {
        // 分享完成后清理 tmp 文件
        [weakSelf cleanupFile:fileURL];
    };
    [vc presentViewController:activityVC animated:YES completion:nil];
}

#pragma mark - Fallback

- (void)fallbackToSafari:(NSURL *)url {
    if (url == nil) return;
    __weak __typeof(self) weakSelf = self;
    [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:^(BOOL ok) {
        __strong __typeof(weakSelf) self = weakSelf;
        if (!ok && self.onEvent) {
            self.onEvent([[TQDownloadFailedEvent alloc]
                          initWithUrl:url.absoluteString
                               reason:@"Safari fallback: openURL returned NO"]);
        }
    }];
}

#pragma mark - Helpers

- (BOOL)canPresentOn:(UIViewController *)vc {
    return !vc.isBeingDismissed && !vc.isBeingPresented && vc.view.window != nil;
}

/// 归一化下载文件名，防止路径穿越。
///
/// 规则：
/// 1. 只取最后一段路径（lastPathComponent 剔除 ../ 等路径成分）
/// 2. 替换分隔符 / HFS 冒号 / 空字符为 _
/// 3. 纯 . / .. / 空名 → 时间戳兜底名
/// 4. 超长（>200）截断，防超长名写入失败
+ (NSString *)sanitizedFilename:(NSString *)raw {
    NSString *name = [raw ?: @"" lastPathComponent];
    NSCharacterSet *illegal = [NSCharacterSet characterSetWithCharactersInString:@"/\\:\0"];
    name = [[name componentsSeparatedByCharactersInSet:illegal] componentsJoinedByString:@"_"];
    name = [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    if (name.length == 0 || [name isEqualToString:@"."] || [name isEqualToString:@".."]) {
        name = [NSString stringWithFormat:@"download_%ld", (long)[[NSDate date] timeIntervalSince1970]];
    }
    if (name.length > 200) {
        name = [name substringFromIndex:name.length - 200];
    }
    return name;
}

- (void)cleanupFile:(NSURL *)fileURL {
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:NULL];
}

@end
