//
//  FileImportManager.swift
//  PadelClub
//
//  Created by Razmig Sarkissian on 01/03/2024.
//

import Foundation
import LeStorage

enum FileImportManagerError: LocalizedError {
    case unknownFormat
    
    var errorDescription: String? {
        switch self {
        case .unknownFormat:
            return "Format non reconnu"
        }
    }
}

class FileImportManager {
    static let shared = FileImportManager()
    
    func updatePlayers(isMale: Bool, players: inout [FederalPlayer]) {
        guard let mostRecentDateAvailable = URL.importDateFormatter.date(from: "05-2024") else { return }
        let replacements: [(Character, Character)] = [("Á", "ç"), ("‡", "à"), ("Ù", "ô"), ("Ë", "è"), ("Ó", "î"), ("Î", "ë"), ("…", "É"), ("Ô", "ï"), ("È", "é"), ("«", "Ç"), ("»", "È")]

        var playersLeft = players
        SourceFileManager.shared.allFilesSortedByDate(isMale).filter({ $0.dateFromPath.isEarlierThan(mostRecentDateAvailable) }).forEach({ url in
            if playersLeft.isEmpty == false {
                let federalPlayers = readCSV(inputFile: url)
                let replacementsCharacters = url.dateFromPath.monthYearFormatted != "04-2024" ? [] : replacements
                
                playersLeft.forEach { importedPlayer in
                    if let federalPlayer = federalPlayers.first(where: { $0.license == importedPlayer.license }) {
                        var lastName = federalPlayer.lastName
                        lastName.replace(characters: replacementsCharacters)
                        var firstName = federalPlayer.firstName
                        firstName.replace(characters: replacementsCharacters)
                        importedPlayer.lastName = lastName.trimmed.uppercased()
                        importedPlayer.firstName = firstName.trimmed.capitalized
                    }
                }
                playersLeft.removeAll(where: { $0.lastName.isEmpty == false })
            }
        })
    }
    
    func foundInWomenData(license: String?) -> Bool {
        guard let license = license?.strippedLicense else {
            return false
        }
        do {
            return try SourceFileManager.shared.allFilesSortedByDate(false).first(where: {
                let fileContent = try String(contentsOf: $0)
                return fileContent.contains(";\(license);")
            }) != nil
        } catch {
            print("history", error)
            return false
        }
    }
    
    func foundInMenData(license: String?) -> Bool {
        guard let license = license?.strippedLicense else {
            return false
        }
        do {
            return try SourceFileManager.shared.allFilesSortedByDate(true).first(where: {
                let fileContent = try String(contentsOf: $0)
                return fileContent.contains(";\(license);")
            }) != nil
        } catch {
            print("history", error)
            return false
        }
    }

    enum FileProvider: CaseIterable, Identifiable {
        var id: Self { self }

        case frenchFederation
        case padelClub
        case custom
        
        var localizedLabel: String {
            switch self {
            case .padelClub:
                return "Padel Club"
            case .frenchFederation:
                return "FFT"
            case .custom:
                return "Personnalisé"
            }
        }
    }
    
    struct TeamHolder: Identifiable {
        let id: UUID = UUID()
        let players: Set<PlayerRegistration>
        let weight: Int
        let tournamentCategory: TournamentCategory
        let previousTeam: TeamRegistration?
        var registrationDate: Date? = nil
        var name: String? = nil

        init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) {
            self.players = Set(players)
            self.tournamentCategory = tournamentCategory
            self.name = name
            self.previousTeam = previousTeam
            if players.count < 2 {
                let s = players.compactMap { $0.sex?.rawValue }
                var missing = tournamentCategory.mandatoryPlayerType()
                s.forEach { i in
                    if let index = missing.firstIndex(of: i) {
                        missing.remove(at: index)
                    }
                }
                let significantPlayerCount = 2
                let pl = players.prefix(significantPlayerCount).map { $0.computedRank }
                let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 100_000 : 10_000) }).prefix(significantPlayerCount)
                self.weight = pl.reduce(0,+) + missingPl.reduce(0,+)
            } else {
                self.weight = players.map { $0.computedRank }.reduce(0,+)
            }
            self.registrationDate = registrationDate
        }
                
        func index(in teams: [TeamHolder]) -> Int? {
            teams.firstIndex(where: { $0.id == id })
        }

        func formattedSeedIndex(index: Int?) -> String {
            if let index {
                return "#\(index + 1)"
            } else {
                return "###"
            }
        }

        func formattedSeed(in teams: [TeamHolder]) -> String {
            if let index = index(in: teams) {
                return "#\(index + 1)"
            } else {
                return "###"
            }
        }
    }
    
    static let FFT_ASSIMILATION_WOMAN_IN_MAN = "A calculer selon la pondération en vigueur"
    
    func createTeams(from fileContent: String, tournament: Tournament, fileProvider: FileProvider = .frenchFederation) async throws -> [TeamHolder] {
        
        switch fileProvider {
        case .frenchFederation:
            return try await _getFederalTeams(from: fileContent, tournament: tournament)
        case .padelClub:
            return await _getPadelClubTeams(from: fileContent, tournament: tournament)
        case .custom:
            return await _getPadelBusinessLeagueTeams(from: fileContent, tournament: tournament)
        }
    }
    
    func importDataFromFFT() async -> String? {
        if let importingDate = SourceFileManager.shared.mostRecentDateAvailable {
            for source in SourceFile.allCases {
                for fileURL in source.currentURLs {
                    let p = readCSV(inputFile: fileURL)
                    await importingChunkOfPlayers(p, importingDate: importingDate)
                }
            }
            return URL.importDateFormatter.string(from: importingDate)
        }
        return nil
    }
    

    func readCSV(inputFile: URL) -> [FederalPlayer] {
        do {
            let fileContent = try String(contentsOf: inputFile)
            return loadFromCSV(fileContent: fileContent, isMale: inputFile.manData)
        } catch {
            print("error: \(error)") // to do deal with errors
        }
        return []
    }
    
    func loadFromCSV(fileContent: String, isMale: Bool) -> [FederalPlayer] {
        let lines = fileContent.components(separatedBy: "\n")
        return lines.compactMap { line in
            if line.components(separatedBy: ";").count < 10 {
            } else {
                let data = line.components(separatedBy: ";").joined(separator: "\n")
                return FederalPlayer(data, isMale: isMale)
            }
            return nil
        }
    }

    func importingChunkOfPlayers(_ players: [FederalPlayer], importingDate: Date) async {
        for chunk in players.chunked(into: 2000) {
            await PersistenceController.shared.batchInsertPlayers(chunk, importingDate: importingDate)
        }
    }
    
    private func _getFederalTeams(from fileContent: String, tournament: Tournament) async throws -> [TeamHolder] {
        let lines = fileContent.components(separatedBy: "\n")
        guard let firstLine = lines.first else { return [] }
        var separator = ","
        if firstLine.contains(";") {
            separator = ";"
        }
        let headerCount = firstLine.components(separatedBy: separator).count
        
        guard headerCount > 12 else {
            throw FileImportManagerError.unknownFormat
        }
        
        var results: [TeamHolder] = []
        if headerCount <= 18 {
            Array(lines.dropFirst()).chunked(into: 2).forEach { teamLines in
                if teamLines.count == 2 {
                    let dataOne = teamLines[0].replacingOccurrences(of: "\"", with: "").components(separatedBy: separator)
                    var dataTwo = teamLines[1].replacingOccurrences(of: "\"", with: "").components(separatedBy: separator)
                    if dataOne[11] != dataTwo[3] || dataOne[12] != dataTwo[4] {
                        if let found = lines.map({ $0.replacingOccurrences(of: "\"", with: "").components(separatedBy: separator) }).first(where: { components in
                            return dataOne[11] == components[3] && dataOne[12] == components[4]
                        }) {
                            dataTwo = found
                        }
                    }
                    if dataOne.count == dataTwo.count {
                        let category = dataOne[0]
                        
                        var tournamentCategory: TournamentCategory {
                            switch category {
                            case "Double Messieurs":
                                return .men
                            case "Double Dames":
                                return .women
                            case "Double Mixte":
                                return .mix
                            default:
                                return .men
                            }
                        }
                        
                        let resultOne = Array(dataOne.dropFirst(3).dropLast())
                        let resultTwo = Array(dataTwo.dropFirst(3).dropLast())
                        let sexUnknown: Bool = (resultOne.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true) || (resultTwo.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true)

                        var sexPlayerOne : Int {
                            switch tournamentCategory {
                            case .unlisted: return 1
                            case .men: return 1
                            case .women: return 0
                            case .mix: return 0
                            }
                        }
                        
                        var sexPlayerTwo : Int {
                            switch tournamentCategory {
                            case .unlisted: return 1
                            case .men: return 1
                            case .women: return 0
                            case .mix: return 1
                            }
                        }
                        
                        let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown)
                        playerOne.setComputedRank(in: tournament)
                        let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown)
                        playerTwo.setComputedRank(in: tournament)
                        let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]), tournament: tournament)
                        results.append(team)
                    }
                }
            }
            return results
        } else {
            lines.dropFirst().forEach { line in
                let data = line.components(separatedBy: separator)
                if data.count > 18 {
                    let category = data[0]
                    let sexUnknown: Bool = (data.last?.hasPrefix(FileImportManager.FFT_ASSIMILATION_WOMAN_IN_MAN) == true)
                    var tournamentCategory: TournamentCategory {
                        switch category {
                        case "Double Messieurs":
                            return .men
                        case "Double Dames":
                            return .women
                        case "Double Mixte":
                            return .mix
                        default:
                            return .men
                        }
                    }
                    let result = Array(data.dropFirst(3).dropLast())
                    
                    var sexPlayerOne : Int {
                        switch tournamentCategory {
                        case .unlisted: return 1
                        case .men: return 1
                        case .women: return 0
                        case .mix: return 1
                        }
                    }
                    
                    var sexPlayerTwo : Int {
                        switch tournamentCategory {
                        case .unlisted: return 1
                        case .men: return 1
                        case .women: return 0
                        case .mix: return 0
                        }
                    }
                    
                    let playerOne = PlayerRegistration(federalData: Array(result[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown)
                    playerOne.setComputedRank(in: tournament)
                    let playerTwo = PlayerRegistration(federalData: Array(result[8...]), sex: sexPlayerTwo, sexUnknown: sexUnknown)
                    playerTwo.setComputedRank(in: tournament)
                    
                    let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]), tournament: tournament)
                    results.append(team)
                }
            }
            return results
        }
    }

    private func _getPadelClubTeams(from fileContent: String, tournament: Tournament) async -> [TeamHolder] {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "fr_FR")

        // Set the date format to match the input string
        dateFormatter.dateFormat = "EEE dd MMM yyyy 'à' HH:mm"


        var lines = fileContent.components(separatedBy: "\n\n")
        var results: [TeamHolder] = []
        let fetchRequest = ImportedPlayer.fetchRequest()
        let federalContext = PersistenceController.shared.localContainer.viewContext
        lines.removeAll(where: { $0.contains("Liste d'attente")})
        lines.forEach { team in
            let data = team.components(separatedBy: "\n")
            let players = team.licencesFound()
            fetchRequest.predicate = NSPredicate(format: "license IN %@", players)
            let found = try? federalContext.fetch(fetchRequest)
            let registeredPlayers = found?.map({ importedPlayer in
                let player = PlayerRegistration(importedPlayer: importedPlayer)
                player.setComputedRank(in: tournament)
                return player
            })
            if let registeredPlayers, registeredPlayers.isEmpty == false {
                var registrationDate: Date? {
                    if let registrationDateData = data[safe:2]?.replacingOccurrences(of: "Inscrit le ", with: "") {
                        return dateFormatter.date(from: registrationDateData)
                    }
                    return nil
                }
                
                var name: String? {
                    if let name = data[safe:3] {
                        return name
                    }
                    return nil
                }
                
                let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament)
                results.append(team)
            }
        }
        
        return results
    }
    
    private func _getPadelBusinessLeagueTeams(from fileContent: String, tournament: Tournament) async -> [TeamHolder] {
        let lines = fileContent.replacingOccurrences(of: "\"", with: "").components(separatedBy: "\n")
        guard let firstLine = lines.first else { return [] }
        var separator = ","
        if firstLine.contains(";") {
            separator = ";"
        }
        let results: [TeamHolder] = lines.chunked(into: 2).map { team in
            var teamName: String? = nil
            let players = team.map { player in
                let data = player.components(separatedBy: separator)
                let firstName : String = data[safe: 2]?.trimmed ?? ""
                let lastName : String = data[safe: 3]?.trimmed ?? ""
                let sex: PlayerRegistration.PlayerSexType = data[safe: 0] == "f" ? PlayerRegistration.PlayerSexType.female : PlayerRegistration.PlayerSexType.male
                if data[safe: 1]?.trimmed != nil {
                    teamName = data[safe: 1]?.trimmed
                }
                let phoneNumber : String? = data[safe: 4]?.trimmed
                let email : String? = data[safe: 5]?.trimmed
                //let level : String? = data[safe: 6]?.trimmed
                let player = PlayerRegistration(firstName: firstName, lastName: lastName, sex: sex, phoneNumber: phoneNumber, email: email)
                return player
            }

            return TeamHolder(players: players, tournamentCategory: .men, previousTeam: nil, name: teamName, tournament: tournament)
        }
        return results
    }
}
