import { Request, RequestOptions, RequestSuccess, RequestFail, RequestTask, UploadFileOptions, UploadFile, UploadTask, OnProgressUpdateResult, UploadFileSuccess, UploadFileProgressUpdateCallback, DownloadFileProgressUpdateCallback, DownloadFileOptions, OnProgressDownloadResult, DownloadFile, DownloadFileSuccess } from '../interface';
import { NetworkManager, NetworkRequestListener, NetworkUploadFileListener, NetworkDownloadFileListener } from './network/NetworkManager.uts'
import { Data, HTTPURLResponse, NSError, NSNumber, ComparisonResult, RunLoop, Thread, JSONSerialization } from 'Foundation';
import { StatusCode } from './network/StatusCode.uts';
import { RequestFailImpl, UploadFileFailImpl, DownloadFileFailImpl, getErrcode } from '../unierror';
import { NetworkUtil } from './network/utils/NetworkUtil.uts';
import { CFString } from 'CoreFoundation';
class SimpleNetworkListener<T> extends NetworkRequestListener {
	private param : RequestOptions<T> | null = null;
	private headers : Map<string, any> | null = null;
	private received : number = 0;
	private data : Data = new Data();
	@UTSiOS.keyword("weak")
	public override task : AnyObject | null = null;
	constructor(param : RequestOptions<T>) {
		this.param = param;
		super();
	}

	public override onStart() : void {
	}

	public override onHeadersReceived(statusCode : number, headers : Map<string, any>) : void {
		this.headers = headers;
		
	}
	public override onDataReceived(data : Data) : void {
		this.received += Number.from(data.count);
		this.data.append(data);
	}

	public override onFinished(response : HTTPURLResponse) : void {
		try {
			let headers = response.allHeaderFields as Map<string, any>;
			let kParam = this.param;
			let result = {};
			result['statusCode'] = response.statusCode;
			result['statusText'] = StatusCode.getStatus(new String(response.statusCode));
			if (headers != null) {
				result['header'] = headers;
			}
			let strData = this.readStringFromData(this.data, response.textEncodingName);


			let type = kParam?.responseType != null ? kParam?.responseType : kParam?.dataType;

			if (type == null && headers != null) {

				for (entry in headers) {
					let key = entry.key;
					if (key.caseInsensitiveCompare("Content-Type") == ComparisonResult.orderedSame) {
						type = headers[key] as string;
					}
				}
			}


			result['data'] = this.parseData(this.data, strData, type);

			if (result['data'] == null) {
				let failResult = new RequestFailImpl(getErrcode(100001));
				let fail = kParam?.fail;
				let complete = kParam?.complete;
				fail?.(failResult);
				complete?.(failResult);
				return
			}

			let tmp = new RequestSuccess<T>({
				data: result['data']! as T,
				statusCode: (new NSNumber(value = response.statusCode)),
				header: result['header'] ?? "",
				cookies: this.parseCookie(this.headers)
			})
			let success = kParam?.success;
			let complete = kParam?.complete;
			success?.(tmp);
			complete?.(tmp);
		} catch (e) {
		}
		if (this.task != null) {
			UTSiOS.destroyInstance(this.task!);
		}
	}

	public override onFail(error : NSError) : void {
		let kParam = this.param;
		let code = (error as NSError).code;
		let errCode = code;
		let cause = error.localizedDescription;
		if (code == -1001) {
			errCode = 5;
		} else if (code == -1004) {
			errCode = 1000;
		} else if (code == -1009) {
			errCode = 600003;
		} else {
			errCode = 602001;
		}

		let failResult = new RequestFailImpl(getErrcode(Number.from(errCode)));
		failResult.cause = new SourceError(cause);

		let fail = kParam?.fail;
		let complete = kParam?.complete;
		fail?.(failResult);
		complete?.(failResult);
		if (this.task != null) {
			UTSiOS.destroyInstance(this.task!);
		}
	}


	private readStringFromData(data : Data, type : string | null) : string | null {
		let result : string | null = null;
		let finalType = type;
		if (finalType == null || finalType!.length == 0) {
			finalType = "utf-8";
		}

		let cfEncoding = CFStringConvertIANACharSetNameToEncoding(finalType as CFString);
		if (cfEncoding != kCFStringEncodingInvalidId) {
			let stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
			let encode = new String.Encoding(rawValue = stringEncoding);
			result = new String(data = data, encoding = encode);
		}
		return result;
	}

	private parseData(data : Data | null, dataStr : string | null, parseType : string | null) : any | null {
		if (`${type(of = T.self)}` == "Any.Protocol" || `${type(of = T.self)}` == "Optional<Any>.Type") {
			if (parseType != null && parseType!.contains("json")) {
				if (dataStr == null || dataStr!.length == 0) {
					return {};
				}

				return this.tryParseJson(data!, dataStr!);
			} else if (parseType == 'jsonp') {
				if (dataStr == null || dataStr!.length == 0) {
					return {};
				}
				let start = dataStr!.indexOf('(');
				let end = dataStr!.indexOf(')');
				if (start == 0 || start >= end) {
					return {};
				}
				start += 1;
				let tmp = dataStr!.slice(start, end);
				return this.tryParseJson(data!, tmp);
			} else if (parseType == 'arraybuffer') {
				if (data == null) {
					return data;
				}
				return ArrayBuffer.fromData(data!)
			} else {
				//dataStr如果解码失败是空的时候，还需要继续尝试解码。极端情况，服务器不是utf8的，所以字符解码会出现乱码，所以特殊处理一下非utf8的字符。
				if (data == null) {
					return data;
				}

				let currentStr : string | null = dataStr;
				//todo 等uts支持swift文件混编的时候，再进行处理。
				// if (currentStr == null) {
				// 	let data = cleanUTF8(data);
				// 	if (data != null) {
				// 		currentStr = new String(data = data, encoding = String.Encoding.utf8);
				// 	}
				// }

				if (currentStr == null) {
					currentStr = new String(data = data!, encoding = String.Encoding.utf8);
				}
				// utf8 如果失败了，就用ascii，虽然几率很小。
				if (currentStr == null) {
					currentStr = new String(data = data!, encoding = String.Encoding.ascii);
				}
				return this.tryParseJson(data!, currentStr!) ?? currentStr;
			}
		} else {
			if (dataStr == null || dataStr!.length == 0) {
				return null;
			}
			return JSON.parse<T>(dataStr!)
		}
	}

	/**
	 * 尝试进行json解析，如果失败了就说明不是json数据
	 */
	private tryParseJson(data : Data, jsonStr : string) : any | null {
		let jsonData : any | null = null;
		try {
			const res = UTSiOS.try(JSONSerialization.jsonObject(with = data, options = JSONSerialization.ReadingOptions.allowFragments), "?")
			if (res != null) {
				jsonData = JSON.parse(jsonStr)
			}
		} catch (e) {
		}
		return jsonData;
	}

	private parseCookie(header : Map<string, any> | null) : string[] {
		if (header == null) {
			return []
		}
		let cookiesStr = header!.get('Set-Cookie') as string | null
		if (cookiesStr == null) {
			cookiesStr = header!.get('set-cookie') as string | null
		}
		if (cookiesStr == null) {
			return []
		}
		let cookiesArr = new Array<string>()
		if (cookiesStr!.charAt(0) == "[" && cookiesStr!.charAt(cookiesStr!.length - 1) == "]") {
			cookiesStr = cookiesStr!.slice(1, -1)
		}

		const handleCookiesArr = cookiesStr!.split(';')
		for (let i = 0; i < handleCookiesArr.length; i++) {
			if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
				cookiesArr.push(handleCookiesArr[i].replace(',', ''))
			} else {
				cookiesArr.push(handleCookiesArr[i])
			}
		}
		cookiesArr = cookiesArr.join(';').split(',')

		return cookiesArr
	}
}

class UploadNetworkListener extends NetworkUploadFileListener {
	private param : UploadFileOptions | null = null;
	public override progressListeners : Array<UploadFileProgressUpdateCallback> = [];
	private data : Data = new Data();
	@UTSiOS.keyword("weak")
	public override task : AnyObject | null = null;

	constructor(param : UploadFileOptions) {
		this.param = param;
		super();
	}

	public override onProgress(progressUpdate : OnProgressUpdateResult) {
		if (this.progressListeners.length != 0) {
			for (let i = 0; i < this.progressListeners.length; i++) {
				let listener = this.progressListeners[i];
				listener(progressUpdate)
			}
		}
	}

	public override onDataReceived(data : Data) : void {
		this.data.append(data);
	}

	public override onFinished(response : HTTPURLResponse) : void {
		try {
			let kParam = this.param;
			let strData = this.readStringFromData(this.data, response.textEncodingName);
			if (strData == null) {
				strData = new String(data = this.data, encoding = String.Encoding.utf8);
				// utf8 如果失败了，就用ascii，几率很小。
				if (strData == null) {
					strData = new String(data = this.data, encoding = String.Encoding.ascii);
				}
			}

			let successResult : UploadFileSuccess = {
				data: strData ?? "",
				statusCode: response.statusCode
			}
			let success = kParam?.success;
			let complete = kParam?.complete;
			success?.(successResult);
			complete?.(successResult);
		} catch (e) {
		}
		this.progressListeners.splice(0, this.progressListeners.length)
		if (this.task != null) {
			UTSiOS.destroyInstance(this.task!);
		}
	}

	public override onFail(error : NSError) : void {
		let kParam = this.param;
		let code = (error as NSError).code;
		let errCode = code;
		let cause = error.localizedDescription;
		if (code == -1001) {
			errCode = 5;
		} else if (code == -1004) {
			errCode = 1000;
		} else if (code == -1009) {
			errCode = 600003;
		} else {
			errCode = 602001;
		}

		let failResult = new UploadFileFailImpl(getErrcode(Number.from(errCode)));
		failResult.cause = new SourceError(cause);
		let fail = kParam?.fail;
		let complete = kParam?.complete;
		fail?.(failResult);
		complete?.(failResult);
		this.progressListeners.splice(0, this.progressListeners.length)
		if (this.task != null) {
			UTSiOS.destroyInstance(this.task!);
		}
	}


	private readStringFromData(data : Data, type : string | null) : string | null {
		let result : string | null = null;
		let finalType = type;
		if (finalType == null || finalType!.length == 0) {
			finalType = "utf-8";
		}

		let cfEncoding = CFStringConvertIANACharSetNameToEncoding(finalType as CFString);
		if (cfEncoding != kCFStringEncodingInvalidId) {
			let stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
			let encode = new String.Encoding(rawValue = stringEncoding);
			result = new String(data = data, encoding = encode);
		}
		return result;
	}
}

class DownloadNetworkListener extends NetworkDownloadFileListener {
	public override options : DownloadFileOptions | null = null;
	public override progressListeners : Array<DownloadFileProgressUpdateCallback> = [];
	private data : Data = new Data();
	@UTSiOS.keyword("weak")
	public override task : AnyObject | null = null;

	constructor(options : DownloadFileOptions) {
		super();
		this.options = options;
	}

	public override onProgress(progressUpdate : OnProgressDownloadResult) {
		if (this.progressListeners.length != 0) {
			for (let i = 0; i < this.progressListeners.length; i++) {
				let listener = this.progressListeners[i];
				listener(progressUpdate)
			}
		}
	}

	public override onFinished(response : HTTPURLResponse, filePath : string) : void {
		try {
			let kParam = this.options;
			let tmp : DownloadFileSuccess = {
				tempFilePath: filePath,
				statusCode: response.statusCode
			};
			let success = kParam?.success;
			let complete = kParam?.complete;
			success?.(tmp);
			complete?.(tmp);
		} catch (e) {
		}
		this.progressListeners.splice(0, this.progressListeners.length)
		if (this.task != null) {
			UTSiOS.destroyInstance(this.task!);
		}
	}

	public override onFail(error : NSError) : void {
		let kParam = this.options;
		let code = (error as NSError).code;
		let errCode = code;
		let cause = error.localizedDescription;
		if (code == -1001) {
			errCode = 5;
		} else if (code == -1004) {
			errCode = 1000;
		} else if (code == -1009) {
			errCode = 600003;
		} else {
			errCode = 602001;
		}

		let failResult = new DownloadFileFailImpl(getErrcode(Number.from(errCode)));
		let codeCause = StatusCode.getStatus(new String(code))
		const sourceError = new SourceError(codeCause == "unknown status" ? cause : codeCause);
		sourceError.code = Number.from(code);
		failResult.cause = sourceError;
		let fail = kParam?.fail;
		let complete = kParam?.complete;
		fail?.(failResult);
		complete?.(failResult);
		this.progressListeners.splice(0, this.progressListeners.length)
		if (this.task != null) {
			UTSiOS.destroyInstance(this.task!);
		}
	}
}


// export const request : Request = (options : RequestOptions) : RequestTask => {
// 	return NetworkManager.getInstance().request(options, new SimpleNetworkListener(options));
// }

export function request<T>(options : RequestOptions<T>, _t : T.Type) : RequestTask {
	return NetworkManager.getInstance().request(options, new SimpleNetworkListener<T>(options));
}


export const uploadFile : UploadFile = (options : UploadFileOptions) : UploadTask => {
	return NetworkManager.getInstance().uploadFile(options, new UploadNetworkListener(options));
}

export const downloadFile : DownloadFile = (options : DownloadFileOptions) : DownloadTask => {
	return NetworkManager.getInstance().downloadFile(options, new DownloadNetworkListener(options));
}