import Foundation
import RealmSwift
import DifferenceKit

class VehicleName: Object, Decodable, Cloneable {
    @objc dynamic var original: String?
    @objc dynamic var normalized: String?
    
    required init(copy: VehicleName) {
        self.original = copy.original
        self.normalized = copy.normalized
    }
    
    required init() {
    }
}

class VehicleBrand: Object, Decodable, Cloneable {
    @objc dynamic var name: VehicleName?
    @objc dynamic var logo: String?
    
    required init(copy: VehicleBrand) {
        self.name = copy.name?.clone()
        self.logo = copy.logo
    }
    
    required init() {
        super.init()
    }
}

class VehicleModel: Object, Decodable, Cloneable {
    @objc dynamic var name: VehicleName?
    
    required init(copy: VehicleModel) {
        self.name = copy.name?.clone()
    }
    
    required init() {
        super.init()
    }
}

class VehicleEngine: Object, Decodable, Cloneable {
    @objc dynamic var number: String?
    var volume: RealmOptional<Int> = RealmOptional(0)
    @objc dynamic var powerHp: Float = 0
    var powerKw: RealmOptional<Float> = RealmOptional(0)
    @objc dynamic var fuelType: String?
    
    enum CodingKeys: String, CodingKey {
        case number, volume, powerHp, powerKw, fuelType
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.number = try container.decodeIfPresent(String.self, forKey: .number)
        self.volume = RealmOptional(try container.decodeIfPresent(Int.self, forKey: .volume))
        self.powerHp = try container.decode(Float.self, forKey: .powerHp)
        self.powerKw = RealmOptional(try container.decodeIfPresent(Float.self, forKey: .powerKw))
        self.fuelType = try container.decodeIfPresent(String.self, forKey: .fuelType)
    }
    
    required init() {
        super.init()
    }
    
    required init(copy: VehicleEngine) {
        self.number = copy.number
        self.volume = copy.volume
        self.powerHp = copy.powerHp
        self.powerKw = copy.powerKw
        self.fuelType = copy.fuelType
    }
}

class VehiclePhoto: Object, Decodable, Cloneable {
    @objc dynamic var brand: String?
    @objc dynamic var model: String?
    @objc dynamic var date: TimeInterval = 0
    @objc dynamic var url: String = ""
    
    override var description: String {
        let formatter = DateFormatter()
        formatter.timeZone = TimeZone(identifier:"GMT")
        formatter.dateStyle = .medium
        formatter.timeStyle = .none
        let date = Date(timeIntervalSince1970: self.date/1000)
        let dateStr = formatter.string(from: date)
        return "\(self.brand ?? "") \(self.model ?? "") (\(dateStr))"
    }
    
    required init(copy: VehiclePhoto) {
        self.brand = copy.brand
        self.model = copy.model
        self.date = copy.date
        self.url = copy.url
    }
    
    required init() {
        super.init()
    }
}

enum OwnerType: String, CustomStringConvertible {
    case legal
    case individual
    
    var description: String {
        switch self {
        case .legal: return NSLocalizedString("legal", comment: "Owner type")
        case .individual: return NSLocalizedString("individual", comment: "Owner type")
        }
    }
}

enum SteeringWheelPosition: CustomStringConvertible {
    case left
    case right
    case unknown
    
    var description: String {
        switch self {
        case .left: return "Left"
        case .right: return "Right"
        case .unknown: return "Unknown"
        }
    }
}

class VehicleOwnershipPeriod: Object, Decodable, Cloneable {
    @objc dynamic var lastOperation: String = ""
    @objc dynamic var ownerType: String = ""
    @objc dynamic var from: Int64 = 0
    @objc dynamic var to: Int64 = 0
    @objc dynamic var region: String?
    @objc dynamic var registrationRegion: String?
    @objc dynamic var locality: String?
    @objc dynamic var code: String?
    @objc dynamic var street: String?
    @objc dynamic var building: String?
    @objc dynamic var inn: String?
    
    required init(copy: VehicleOwnershipPeriod) {
        self.lastOperation = copy.lastOperation
        self.ownerType = copy.ownerType
        self.from = copy.from
        self.to = copy.to
        self.region = copy.region
        self.registrationRegion = copy.registrationRegion
        self.locality = copy.locality
        self.code = copy.code
        self.street = copy.street
        self.building = copy.building
        self.inn = copy.inn
    }
    
    required init() {
        super.init()
    }
}

class Vehicle: Object, Decodable, Identifiable, Differentiable, Cloneable, Exportable {
    @objc dynamic var brand: VehicleBrand?
    @objc dynamic var model: VehicleModel?
    @objc dynamic var color: String?
    @objc dynamic var year: Int = 0
    @objc dynamic var category: String?
    @objc dynamic var engine: VehicleEngine?
    @objc private dynamic var number: String = ""
    @objc dynamic var currentNumber: String?
    @objc dynamic var vin1: String?
    @objc dynamic var vin2: String?
    @objc dynamic var sts: String?
    @objc dynamic var pts: String?
    var isRightWheel = RealmOptional<Bool>()
    var isJapanese: RealmOptional<Bool> = RealmOptional<Bool>()
    @objc dynamic var addedDate: TimeInterval = 0
    @objc dynamic var updatedDate: TimeInterval = 0
    @objc dynamic var addedBy: String = ""
    var photos = List<VehiclePhoto>()
    var ownershipPeriods = List<VehicleOwnershipPeriod>()
    var events = List<VehicleEvent>()
    var osagoContracts = List<Osago>()
    var ads = List<VehicleAd>()
    @objc dynamic var debugInfo: DebugInfo?
    
    lazy var formatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .medium
        f.timeStyle = .medium
        return f
    }()
    
    var differenceIdentifier: String { self.number }
    
    func isContentEqual(to source: Vehicle) -> Bool {
        return  self.number == source.number &&
                self.addedDate == source.addedDate &&
                self.updatedDate == source.updatedDate
    }
    
    enum CodingKeys: String, CodingKey {
        case brand
        case model
        case color
        case year
        case category
        case engine
        case number
        case currentNumber
        case vin1
        case vin2
        case sts
        case pts
        case isRightWheel
        case isJapanese
        case addedDate
        case updatedDate
        case addedBy
        case photos
        case ownershipPeriods
        case events
        case osagoContracts
        case ads
        case debugInfo
    }
        
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
        self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
        self.color = try container.decodeIfPresent(String.self, forKey: .color)
        self.year = try container.decodeIfPresent(Int.self, forKey: .year) ?? 0
        self.category = try container.decodeIfPresent(String.self, forKey: .category)
        self.number = try container.decode(String.self, forKey: .number)
        self.engine = try container.decodeIfPresent(VehicleEngine.self, forKey: .engine)
        self.currentNumber = try container.decodeIfPresent(String.self, forKey: .currentNumber)
        self.vin1 = try container.decodeIfPresent(String.self, forKey: .vin1)
        self.vin2 = try container.decodeIfPresent(String.self, forKey: .vin2)
        self.sts = try container.decodeIfPresent(String.self, forKey: .sts)
        self.pts = try container.decodeIfPresent(String.self, forKey: .pts)
        self.isRightWheel = RealmOptional(try container.decodeIfPresent(Bool.self, forKey: .isRightWheel))
        self.isJapanese = RealmOptional(try container.decodeIfPresent(Bool.self, forKey: .isJapanese))
        self.addedDate = (try container.decode(TimeInterval.self, forKey: .addedDate))/1000
        self.addedBy = try container.decode(String.self, forKey: .addedBy)
        self.debugInfo = try container.decodeIfPresent(DebugInfo.self, forKey: .debugInfo)
        self.updatedDate = (try container.decode(TimeInterval.self, forKey: .updatedDate))/1000
        
        if let photosArray = try container.decodeIfPresent([VehiclePhoto].self, forKey: .photos) {
            self.photos.append(objectsIn: photosArray)
        }
        
        if let ownersipsArray = try container.decodeIfPresent([VehicleOwnershipPeriod].self, forKey: .ownershipPeriods) {
            self.ownershipPeriods.append(objectsIn: ownersipsArray)
        }
        
        if let eventsArray = try container.decodeIfPresent([VehicleEvent].self, forKey: .events) {
            self.events.append(objectsIn: eventsArray)
        }
        
        if let osago = try container.decodeIfPresent([Osago].self, forKey: .osagoContracts) {
            self.osagoContracts.append(objectsIn: osago)
        }
        
        if let ads = try container.decodeIfPresent([VehicleAd].self, forKey: .ads) {
            self.ads.append(objectsIn: ads)
        }
    }
    
    required init() {
        super.init()
    }
	
	init(_ number: String) {
		self.number = number
		self.addedDate = Date().timeIntervalSince1970
        self.updatedDate = self.addedDate
	}
    
    func getNumber() -> String {
        return self.number
    }
    
    override static func primaryKey() -> String? {
        return "number"
    }
    
    override class func ignoredProperties() -> [String] {
        return ["id", "identifier", "differenceIdentifier", "formatter"]
    }
    
    var unrecognized: Bool {
        return self.brand == nil
    }
    
    var outdated: Bool {
        if let current = self.currentNumber {
            return current != self.number
        } else {
            return false
        }
    }
    
    // MARK: - Cloneable
    
    required init(copy: Vehicle) {
        self.brand = copy.brand
        self.model = copy.model
        self.color = copy.color
        self.year = copy.year
        self.category = copy.category
        self.engine = copy.engine
        self.number = copy.number
        self.currentNumber = copy.currentNumber
        self.vin1 = copy.vin1
        self.vin2 = copy.vin2
        self.sts = copy.sts
        self.pts = copy.pts
        self.isRightWheel = copy.isRightWheel
        self.isJapanese = copy.isJapanese
        self.addedDate = copy.addedDate
        self.addedBy = copy.addedBy
        self.updatedDate = copy.updatedDate
        
        let photos = List<VehiclePhoto>()
        photos.append(objectsIn: copy.photos.map { $0.clone() })
        self.photos =  photos
        
        let ownerships = List<VehicleOwnershipPeriod>()
        ownerships.append(objectsIn: copy.ownershipPeriods.map { $0.clone() })
        self.ownershipPeriods = ownerships
        
        let events = List<VehicleEvent>()
        events.append(objectsIn: copy.events.map { $0.clone() })
        self.events = events
        
        let osago = List<Osago>()
        osago.append(objectsIn: copy.osagoContracts.map { $0.clone() })
        self.osagoContracts = osago
        
        let ads = List<VehicleAd>()
        ads.append(objectsIn: copy.ads.map { $0.clone() })
        self.ads = ads
    }
    
    // MARK: - Exportable
    
    static var csvHeader: String {
        return "Model, Color, Year, Plate Number, VIN, STS, PTS, Added Date, Updated date"
    }
    
    var csvLine: String {
        let model = self.brand?.name?.original ?? "<unknown>"
        let added = self.formatter.string(from: Date(timeIntervalSince1970: self.addedDate))
        let updated = self.formatter.string(from: Date(timeIntervalSince1970: self.updatedDate))
        return String(format: "\"%@\", %@, %d, %@, %@, %@, %@, \"%@\", \"%@\"", model, self.color ?? "", self.year, self.number, self.vin1 ?? "", self.sts ?? "", self.pts ?? "", added, updated)
    }
}
