import UIKit
import WebKit
import CommonCrypto
import RxSwift
import PKHUD

struct TokenResponse: Codable {
    var id_token: String
    var refresh_token: String?
    var access_token: String
    var expires_in: Int
    var token_type: String
    var scope: String
}

class GoogleSignInController: UIViewController, WKNavigationDelegate {
	@IBOutlet weak var webView: WKWebView!
	
    private var bag = DisposeBag()
	private var codeVerifier: String = ""
	public var completion: (() -> Void)?
	
	override func viewDidLoad() {
		super.viewDidLoad()
		self.webView.navigationDelegate = self
		
#if targetEnvironment(macCatalyst)
		self.webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15"
#else
		self.webView.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Mobile/15E148 Safari/604.1"
#endif
		
		self.codeVerifier = UUID().uuidString
		let codeChallenge = self.sha256(string: self.codeVerifier) ?? ""
		
		let authUrlString = Constants.googleAuthURL
			+ "?response_type=code"
			+ "&code_challenge_method=S256"
			+ "&scope=email%20profile"
			+ "&redirect_uri=" + Constants.googleRedirectURL
			+ "&client_id=" + Constants.fbClientId
			+ "&code_challenge=" + codeChallenge
		
		if let url = URL(string: authUrlString) {
			let request = URLRequest(url: url)
			self.webView.load(request)
		}
	}
	
	func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
		if let url = navigationAction.request.url {
			if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
				if let queryItems = components.queryItems {
					if let code = queryItems.first(where: { $0.name == "code" })?.value {
						decisionHandler(.cancel)
                        self.getToken(code: code)
                            .flatMap { Api.fbVerifyAssertion(provider: "google.com", idToken: $0.id_token, accessToken: $0.access_token) }
                            .observeOn(MainScheduler.instance)
                            .subscribe(onSuccess: { _ in
                                self.dismiss(animated: true, completion: self.completion)
                            }, onError: { error in
                                HUD.flash(.labeledError(title: nil, subtitle: error.localizedDescription))
                            })
                            .disposed(by: self.bag)
						return
					}
				}
			}
		}
		
		decisionHandler(.allow)
	}
	
	@IBAction func close(_ sender: UIBarButtonItem) {
		self.dismiss(animated: true, completion: nil)
	}
	
	func sha256(string: String) -> String? {
		guard let data = string.data(using: .utf8) else { return nil }
		
		var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
		data.withUnsafeBytes {
			_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
		}
		
		return String(data: Data(Base64FS.encode(data: hash)), encoding: .utf8)?.trimmingCharacters(in: CharacterSet(charactersIn: "="))
	}
	
	func getToken(code: String) -> Single<TokenResponse> {
		let tokenUrlString = Constants.googleTokenURL
			+ "?grant_type=authorization_code"
			+ "&code=" + code
			+ "&redirect_uri=" + Constants.googleRedirectURL
			+ "&client_id=" + Constants.fbClientId
			+ "&code_verifier=" + self.codeVerifier
		
		if let url = URL(string: tokenUrlString) {
			var request = URLRequest(url: url)
			request.httpMethod = "POST"
            return URLSession.shared.rx.data(request: request).asSingle().map { data in
                return try JSONDecoder().decode(TokenResponse.self, from: data)
			}
		} else {
			let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Bad URL"])
            return Single.error(error)
		}
	}
}
