//
//  account.swift
//  74 桌
//
//  Created by yunli on 2023/5/20.
//

import Alamofire
import SwiftUI

struct Constant {
    var cookiesDefaultsKey: String
}

let Constants: Constant = .init(cookiesDefaultsKey: "JSESSIONID")

class CookieHandler {
    static let shared: CookieHandler = .init()

    let defaults = UserDefaults.standard
    let cookieStorage = HTTPCookieStorage.shared

    func getCookie(forURL url: String) -> [HTTPCookie] {
        let computedUrl = URL(string: url)
        let cookies = cookieStorage.cookies(for: computedUrl!) ?? []

        return cookies
    }

    func getAllCookie() -> [HTTPCookie] {
        let cookies = cookieStorage.cookies ?? []

        return cookies
    }

    func delCookie() {}
}

struct Login: Encodable {
    let user: String
    let pwd: String
    let cook: String
}

struct UserInfo: Decodable {
    var name: String
    var oid: String
    var id: String
    var xydLink: String
    var serviceLink: String
    var studyLink: String
    var tableData: [[PurpleDatum]]
    init() {
        name = "-"
        oid = "-"
        id = "-"
        xydLink = "-"
        serviceLink = "-"
        studyLink = "-"
        tableData = []
    }
}

class LoginHandler {
    static let sharedInstance = LoginHandler()

    func getCookie(name: String) -> [HTTPCookie] {
        return CookieHandler().getCookie(forURL: "https://debug.sdsz.icu:81/").filter { cookie -> Bool in
            cookie.name == name
        }
    }

    func getAllCookie(name: String) -> [HTTPCookie] {
        return CookieHandler().getAllCookie().filter { cookie -> Bool in
            cookie.name == name
        }
    }

    func fetchLoginCookie(action: @escaping (_: String) -> Void) {
        if let cookie = getCookie(name: "JSESSIONID").first {
            HTTPCookieStorage.shared.deleteCookie(cookie)
            print(cookie)
        }
        let url = "https://debug.sdsz.icu:81/sso/login"

        AF.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).response { _ in
            if let cookie = self.getCookie(name: "JSESSIONID").first {
                action(cookie.value)
            }
        }
    }
}

class FetchHandler {
    static let sharedInstance = FetchHandler()

    func matches(url: String) -> Bool {
        do {
            return try url.matches(of: Regex(#"(^\[\]$|param":"|"workRests"|<tr>|DOCTYPE|in\?username=|requestParams|\"newmessage\"|Sorry, Page Not Found|北师大实验中学--登录|404 Not Found|502 Bad Gateway)"#)).count != 0
        } catch {
            return true
        }
    }

    func getUrl(url: String) -> String {
        var ret: String = url
        ret = ret
            .replacing("dd.sdsz.com.cn", with: "debug.sdsz.icu:81")
            .replacing(/^http:/, with: "https:")
            .replacing("service=http%3A%2F%2Fdebug.sdsz.icu:81", with: "service=http%3A%2F%2Fdd.sdsz.com.cn")
        print("URL: \(ret)")
        return ret
    }

    func workFetch(url: String, action: @escaping (_: String) -> Void) {
        if matches(url: url) {
            action(url)
        } else {
            AF.request(getUrl(url: url), method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseString { res in
                if let resv = res.value {
                    self.workFetch(url: resv, action: action)
                }
            }
        }
    }

    func fetchAny(url: String, action: @escaping (_: String) -> Void) {
        let urlFull = "https://debug.sdsz.icu:81/\(url)"
        workFetch(url: urlFull, action: action)
    }
}

func doLogin(user: String, password: String, action: @escaping (_: Int) -> Void) {
    LoginHandler().fetchLoginCookie { (cookie: String) in
        let login = Login(user: user, pwd: password, cook: cookie)
        print(login)
        AF.request("https://debug.sdsz.icu/andlogin", method: .post, parameters: login, encoder: JSONParameterEncoder.default, headers: nil).responseString { res in
            print("\(res)")
            if res.value == "success" {
                action(1)
            } else {
                action(0)
            }
        }
    }
}

// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)

import Foundation

// MARK: - Welcome

struct Welcome: Codable {
    let workRests: [WorkREST]
    let blocks: [Block]
}

// MARK: - Block

struct Block: Codable {
    let ownObjID, type: String
    let name: String
    let data: [BlockDatum]
    let weekMetas: JSONNull?

    enum CodingKeys: String, CodingKey {
        case ownObjID = "ownObjId"
        case type, name, data, weekMetas
    }
}

// MARK: - BlockDatum

struct BlockDatum: Codable {
    let week: Int
    let weekMeta: WeekMeta
    let data: [PurpleDatum]
}

// MARK: - PurpleDatum

struct PurpleDatum: Codable {
    let day, lesson: Int
    let data: [FluffyDatum]
}

extension PurpleDatum {
    static var coursePurpleDatum = { (course: String) -> PurpleDatum in
        PurpleDatum(day: 0, lesson: 0, data: [FluffyDatum(timetableID: nil, className: "", classID: "", courseName: course, courseID: 0, courseType: 0, placeID: nil, placeName: "", placeSn: "", teacher: [], room: nil, start: "", end: "", timeScope: 0)])
    }
}

// MARK: - FluffyDatum

struct FluffyDatum: Codable {
    let timetableID: JSONNull?
    let className: String
    let classID: String
    let courseName: String
    let courseID, courseType: Int
    let placeID: JSONNull?
    let placeName: String
    let placeSn: String
    let teacher: [Teacher]
    let room: JSONNull?
    let start, end: String
    let timeScope: Int

    enum CodingKeys: String, CodingKey {
        case timetableID = "timetableId"
        case className
        case classID = "classId"
        case courseName
        case courseID = "courseId"
        case courseType
        case placeID = "placeId"
        case placeName, placeSn, teacher, room, start, end, timeScope
    }
}

// MARK: - Teacher

struct Teacher: Codable {
    let userID: Int
    let userName: JSONNull?
    let fullName: String
    let userNo: JSONNull?

    enum CodingKeys: String, CodingKey {
        case userID = "userId"
        case userName, fullName, userNo
    }
}

// MARK: - WeekMeta

struct WeekMeta: Codable {
    let morningRead: Bool
    let night, afternoon, morning, teachingDay: Int
    let time: [Time]
}

// MARK: - Time

struct Time: Codable {
    let seq: Int
    let start, end: String
}

// MARK: - WorkREST

struct WorkREST: Codable {
    let beginTime, endTime, time, lessonName: String
}

// MARK: - Encode/decode helpers

class JSONNull: Codable, Hashable {
    public static func == (_: JSONNull, _: JSONNull) -> Bool {
        return true
    }

    func hash(into _: inout Hasher) {}

    public init() {}

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

var Count: Int = 0

func addCompleted(userInfo: UserInfo, action: @escaping (_: UserInfo) -> Void) {
    Count += 1
    if Count == 3 {
        print(userInfo.tableData)
        action(userInfo)
    }
}

extension String {
    func replace(regex: String, with: String, options: NSRegularExpression.Options) -> String {
        do {
            let RE = try NSRegularExpression(pattern: regex, options: options)
            let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
            return modified
        } catch {
            return "ERR"
        }
    }

    func replace(regex: String, with: String) -> String {
        do {
            let RE = try NSRegularExpression(pattern: regex)
            let modified = RE.stringByReplacingMatches(in: self, range: NSRange(location: 0, length: count), withTemplate: with)
            return modified
        } catch {
            return "ERR"
        }
    }
}

struct TableParam: Encodable {
    var oid: String
}

func getUserInfo(userInfoIn: UserInfo, action: @escaping (_: UserInfo) -> Void) {
    var userInfo: UserInfo = userInfoIn
    FetchHandler().fetchAny(url: "bxn-portal/portal/osforstudent/index") { res in
        if let mat = res.firstMatch(of: /userFullName" value="(.*?)"/) {
            userInfo.name = "\(mat.1)"
        }
        if let mat = res.firstMatch(of: /userId" value="(.*?)"/) {
            userInfo.oid = "\(mat.1)"
        }
        if let mat = res.firstMatch(of: /'https:\/\/service.*?'/) {
            userInfo.serviceLink = "\(mat.output.split(separator: "'")[0])"
            userInfo.studyLink = mat.output.split(separator: "'")[0] + "%2Ffe-pc%2Fb%2Ffe_leco_student%2Fportal%2F%3Fsystem_partition_gId%3D3"
        }
        print(userInfo)
        let tableParam: TableParam = .init(oid: userInfo.oid)
        let header: HTTPHeaders = [
            "user": userInfo.id,
        ]
        AF.request("https://debug.sdsz.icu/getWeek", method: .post, parameters: tableParam, encoder: JSONParameterEncoder.default, headers: header).responseString { res in
            FetchHandler().fetchAny(url: "bxn-timetable/timetable/monitor/homepage/data/student?studentId=\(userInfo.oid)&dateScope=\(res.value ?? "")&_=0") { res in
                do {
                    let data = res.data(using: String.Encoding.utf8)
                    let decoder = JSONDecoder()
                    let kkd = try decoder.decode(Welcome.self, from: data!)
                    let pp = kkd.blocks.first?.data.first?.data ?? []
                    var ret: [[PurpleDatum]] = [], mx: Int = 0
                    for i in 0 ..< pp.count {
                        print(pp[i])
                        mx = max(mx, pp[i].lesson)
                    }
                    ret.append([])
                    ret[0].append(PurpleDatum.coursePurpleDatum("  "))
                    ret[0].append(PurpleDatum.coursePurpleDatum("一 "))
                    ret[0].append(PurpleDatum.coursePurpleDatum("二 "))
                    ret[0].append(PurpleDatum.coursePurpleDatum("三 "))
                    ret[0].append(PurpleDatum.coursePurpleDatum("四 "))
                    ret[0].append(PurpleDatum.coursePurpleDatum("五 "))
                    for i in 1 ... mx {
                        ret.append([])
                        for j in 0 ... 5 {
                            ret[i].append(PurpleDatum(day: 0, lesson: 0, data: []))
                        }
                    }
                    for i in 0 ..< pp.count {
                        ret[pp[i].lesson][pp[i].day] = pp[i]
                    }
                    userInfo.tableData = ret
                } catch {
                    print(error)
                }
                addCompleted(userInfo: userInfo, action: action)
            }
        }
        addCompleted(userInfo: userInfo, action: action)
    }
    FetchHandler().fetchAny(url: "bxn-library/library/jumpExamreport?jumpUrl=http://36.112.23.77/analysis/auto/%23/autoLogin") { res in
        userInfo.xydLink = res
        addCompleted(userInfo: userInfo, action: action)
    }
}

struct loginView: View {
    @State var username: String = ""
    @State var password: String = ""
    @State var btnText: String = "登录"
    @State var btnColor: Color = .blue
    @State var inProgress: Bool = false
    @FocusState private var isFocused: Bool
    @Binding var isLoggedIn: Int
    @Binding var userInfo: UserInfo
    var body: some View {
        VStack {
            Form {
                List {
                    HStack {
                        Image(systemName: "person.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
                        TextField(text: $username, prompt: Text("数字校园号")) {
                            Text("数字校园号")
                        }.focused($isFocused)
                            .keyboardType(.numberPad)
                    }
                    HStack {
                        Image(systemName: "key.fill").foregroundColor(Color(red: 0.7, green: 0.7, blue: 0.7))
                        SecureField(text: $password, prompt: Text("密码")) {
                            Text("密码")
                        }.focused($isFocused)
                    }

                }.onTapGesture {
                    isFocused = false
                }
                HStack {
                    Spacer()
                    Button(action: {
                        inProgress = true
                        doLogin(user: username, password: password) { (ret: Int) in
                            isLoggedIn = ret
                            inProgress = false
                            if ret == 0 {
                                btnText = "登录失败"
                                btnColor = .red
                            } else {
                                userInfo.id = username
                                getUserInfo(userInfoIn: userInfo) { res in
                                    userInfo = res
                                }
                            }
                        }
                    }) {
                        HStack {
                            Text(btnText)
                            if inProgress {
                                ProgressView()
                            } else {
                                Image(systemName: "chevron.right")
                            }
                        }
                    }.foregroundColor(btnColor).buttonStyle(.bordered)
                }
            }
        }
    }
}

struct accountView: View {
    @State var username: String = ""
    @State var password: String = ""
    @FocusState private var isFocused: Bool
    @Binding var isLoggedIn: Int
    @Binding var userInfo: UserInfo
    func delAllCookie(name: String) {
        for cookie in LoginHandler().getAllCookie(name: name) {
            print(cookie)
            HTTPCookieStorage.shared.deleteCookie(cookie)
        }
    }

    var body: some View {
        VStack {
            Form {
                Section(header: Text("基本信息")) {
                    HStack {
                        Text("姓名")
                        Spacer()
                        Text(userInfo.name).foregroundColor(.gray)
                    }
                    HStack {
                        Text("内部 ID")
                        Spacer()
                        Text(userInfo.oid).foregroundColor(.gray)
                    }
                }
                Section {
                    NavigationLink(destination: aboutView()) {
                        Text("关于")
                    }
                }
                Section {
                    Button(action: {
                        isLoggedIn = 0
                        delAllCookie(name: "CASTGC")
                        delAllCookie(name: "JSESSIONID")
                    }) {
                        VStack {
                            Text("退出").foregroundColor(.red)
                        }
                    }
                }
            }
            Spacer()
        }
    }
}

struct aboutView: View {
    var body: some View {
        List {
            Section(footer: Text("实验中学 74 桌是一款面向北师大附属实验中学学生的 App,提供查分及数字校园基础服务。")) {
                Text("实验中学 74 桌")
                HStack {
                    Text("开发者")
                    Spacer()
                    Text("74 开发组").foregroundColor(.gray)
                }
            }
        }.navigationBarTitle("关于")
    }
}

struct accountView_Previews: PreviewProvider {
    @State static var isLoggedIn = 1
    @State static var userInfo: UserInfo = .init()
    static var previews: some View {
        loginView(isLoggedIn: $isLoggedIn, userInfo: $userInfo)
    }
}