//
//  PickerPresenter.swift
//  LunarDatePicker
//
//  Service for presenting picker controllers
//

import UIKit

final class PickerPresenter: PickerPresenting {

  // MARK: - Properties

  private let configurationBuilder: ConfigurationProviding
  private let dateConverter: DateConverting
  private let controllerFactory: PickerControllerFactoryProtocol

  // MARK: - Initialization

  init(
    configurationBuilder: ConfigurationProviding = ConfigurationBuilder(),
    dateConverter: DateConverting = DateConverter(),
    controllerFactory: PickerControllerFactoryProtocol =
      PickerControllerFactory()
  ) {
    self.configurationBuilder = configurationBuilder
    self.dateConverter = dateConverter
    self.controllerFactory = controllerFactory
  }

  // MARK: - PickerPresenting

  func presentPicker(
    params: LDP_PresentParams,
    from viewController: UIViewController
  ) throws {
    let pickerController = createPickerController(
      from: params,
      globalConfig: nil
    )

    switch pickerController {
    case .range(let rangeController):
      configureRangeController(rangeController, params: params, globalConfig: nil)
      rangeController.present(above: viewController)

    case .date(let dateController):
      configureDateController(dateController, params: params, globalConfig: nil)
      dateController.present(above: viewController)
    }
  }

  // MARK: - Internal Methods

  func createPickerController(
    from params: LDP_PresentParams,
    globalConfig: LDP_ConfigParams?,
    onMonthVisible: ((String) -> Void)? = nil
  ) -> PickerControllerType {
    // Determine if prices should be shown
    // nil = don't show prices, empty array = show prices but empty
    let shouldShowPrices = params.prices != nil
    let pricesData = params.prices ?? []

    let pickerConfig = configurationBuilder.buildPickerConfig(
      from: params,
      globalConfig: globalConfig
    )

    if params.mode == .range {
      let controller = controllerFactory.makeRangePickerController(
        config: pickerConfig
      )
      configureCommonDateProperties(controller, params: params, globalConfig: globalConfig)
      configureInitialRangeValue(controller, params: params, globalConfig: globalConfig)
      configurePricesProperties(
        controller,
        prices: pricesData,
        shouldShowPrices: shouldShowPrices,
        onMonthVisible: onMonthVisible
      )
      return .range(controller)
    } else {
      let controller = controllerFactory.makeSinglePickerController(
        config: pickerConfig
      )
      configureCommonDateProperties(controller, params: params, globalConfig: globalConfig)
      configureInitialDateValue(controller, params: params, globalConfig: globalConfig)
      configurePricesProperties(
        controller,
        prices: pricesData,
        shouldShowPrices: shouldShowPrices,
        onMonthVisible: onMonthVisible
      )
      return .date(controller)
    }
  }

  // MARK: - Private Methods

  private func configureCommonDateProperties<T: PickerValue>(
    _ controller: PickerController<T>,
    params: LDP_PresentParams,
    globalConfig: LDP_ConfigParams?
  ) {
    // Get timezone from config, fallback to current timezone
    let timeZone = globalConfig.flatMap { config in
      TimeZone(secondsFromGMT: Int(config.timeZoneOffset * 3600))
    }
    
    if let maximumDate = params.maximumDate {
      controller.maximumDate = dateConverter.dateFromString(maximumDate, timeZone: timeZone)
    }

    if let minimumDate = params.minimumDate {
      controller.minimumDate = dateConverter.dateFromString(minimumDate, timeZone: timeZone)
    }
  }

  private func configureInitialRangeValue(
    _ controller: PickerController<PickerRange>,
    params: LDP_PresentParams,
    globalConfig: LDP_ConfigParams?
  ) {
    guard let initialValue = params.initialValue else { return }

    // Get timezone from config, fallback to current timezone
    let timeZone = globalConfig.flatMap { config in
      TimeZone(secondsFromGMT: Int(config.timeZoneOffset * 3600))
    }

    guard let fromDate = dateConverter.dateFromString(initialValue.from, timeZone: timeZone) else { return }

    if let toDateString = initialValue.to,
       let toDate = dateConverter.dateFromString(toDateString, timeZone: timeZone) {
      // Full range with both from and to
      controller.initialValue = PickerRange(from: fromDate, to: toDate)
    } else {
      // Only from date provided, create a single-day range
      controller.initialValue = PickerRange(from: fromDate, to: fromDate)
    }
  }

  private func configureInitialDateValue(
    _ controller: PickerController<Date>,
    params: LDP_PresentParams,
    globalConfig: LDP_ConfigParams?
  ) {
    guard let initialValue = params.initialValue else { return }

    // Get timezone from config, fallback to current timezone
    let timeZone = globalConfig.flatMap { config in
      TimeZone(secondsFromGMT: Int(config.timeZoneOffset * 3600))
    }

    guard let fromDate = dateConverter.dateFromString(initialValue.from, timeZone: timeZone) else { return }
    controller.initialValue = fromDate
  }

  private func configurePricesProperties<T: PickerValue>(
    _ controller: PickerController<T>,
    prices: [LDP_PriceData],
    shouldShowPrices: Bool,
    onMonthVisible: ((String) -> Void)? = nil
  ) {
    controller.shouldShowPrices = shouldShowPrices
    controller.onMonthVisible = onMonthVisible
  }

  private func configureRangeController(
    _ controller: PickerController<PickerRange>,
    params: LDP_PresentParams,
    globalConfig: LDP_ConfigParams?
  ) {
    controller.doneHandler = { [weak self] selected in
      guard let selected = selected,
        let self = self
      else { return }

      // Get timezone from config, fallback to current timezone
      let timeZone = globalConfig.flatMap { config in
        TimeZone(secondsFromGMT: Int(config.timeZoneOffset * 3600))
      }

      let result = self.dateConverter.rangeFromDates(
        from: selected.fromDate,
        to: selected.toDate,
        timeZone: timeZone
      )
      params.onDone(result)
    }
    
    // Set onSelectFromDate callback
    controller.onSelectFromDate = params.onSelectFromDate
  }

  private func configureDateController(
    _ controller: PickerController<Date>,
    params: LDP_PresentParams,
    globalConfig: LDP_ConfigParams?
  ) {
    controller.doneHandler = { [weak self] selected in
      guard let selected = selected,
        let self = self
      else { return }

      // Get timezone from config, fallback to current timezone
      let timeZone = globalConfig.flatMap { config in
        TimeZone(secondsFromGMT: Int(config.timeZoneOffset * 3600))
      }

      let result = self.dateConverter.rangeFromDate(selected, timeZone: timeZone)
      params.onDone(result)
    }
  }
}

// MARK: - Supporting Types

extension PickerPresenter {
  enum PickerControllerType {
    case range(PickerController<PickerRange>)
    case date(PickerController<Date>)
  }
}
