import DatabaseClient
import Dependencies
import Elementary
import Foundation
import ManualDCore
import PdfClient
import ProjectClient
import Styleguide

extension ViewController.Request {

  func render() async -> AnySendableHTML {

    @Dependency(\.database) var database
    @Dependency(\.projectClient) var projectClient
    @Dependency(\.pdfClient) var pdfClient

    switch route {
    case .test:
      // let projectID = UUID(uuidString: "E796C96C-F527-4753-A00A-EBCF25630663")!
      // return await view {
      //   await ResultView {
      //
      //     // return (
      //     //   try await database.projects.getCompletedSteps(projectID),
      //     //   try await projectClient.calculateDuctSizes(projectID)
      //     // )
      //     return try await pdfClient.html(.mock())
      //   } onSuccess: {
      //     $0
      //     // TestPage()
      //     // TestPage(trunks: result.trunks, rooms: result.rooms)
      //   }
      // }
      // return try! await pdfClient.html(.mock())
      return EmptyHTML()
    case .login(let route):
      switch route {
      case .index(let next):
        return await view {
          LoginForm(next: next)
        }
      case .submit(let login):
        // let _ = try await authenticate(login)
        return await view {
          await ResultView {
            try await authenticate(login)
          } onSuccess: { _ in
            LoggedIn(next: login.next)
          }
        }
      }
    case .signup(let route):
      switch route {
      case .index:
        return await view {
          LoginForm(style: .signup)
        }
      case .submit(let request):
        // Create a new user and log them in.
        return await view {
          await ResultView {
            try await createAndAuthenticate(request)
          } onSuccess: { user in
            MainPage {
              UserProfileForm(userID: user.id, profile: nil, dismiss: false, signup: true)
            }
          }
        }
      case .submitProfile(let profile):
        return await view {
          await ResultView {
            _ = try await database.userProfile.create(profile)
            let userID = profile.userID
            // let user = try currentUser()
            return (
              userID,
              try await database.projects.fetch(userID, .init(page: 1, per: 25)),
              profile.theme
            )
          } onSuccess: { (userID, projects, theme) in
            MainPage(displayFooter: true, theme: theme) {
              ProjectsTable(userID: userID, projects: projects)
            }
          }
        }
      }
    case .project(let route):
      return await route.renderView(on: self)

    case .user(let route):
      return await route.renderView(on: self)
    }
  }

  func view<C: HTML>(
    @HTMLBuilder inner: () async -> C
  ) async -> AnySendableHTML where C: Sendable {
    let inner = await inner()
    let theme = await self.theme

    return MainPage(displayFooter: displayFooter, theme: theme) {
      inner
    }
  }

  var theme: Theme? {
    get async {
      @Dependency(\.database) var database
      guard let user = try? currentUser() else { return nil }
      return try? await database.userProfile.fetch(user.id)?.theme
    }
  }

  var displayFooter: Bool {
    switch route {
    case .login, .signup:
      return false
    default:
      return true
    }
  }
}

extension SiteRoute.View.ProjectRoute {

  func renderView(on request: ViewController.Request) async -> AnySendableHTML {
    @Dependency(\.database) var database
    @Dependency(\.projectClient) var projectClient

    switch self {
    case .index:
      return await request.view {
        await ResultView {
          let user = try request.currentUser()
          return try await (
            user.id,
            database.projects.fetch(user.id, .first)
          )

        } onSuccess: { (userID, projects) in
          ProjectsTable(userID: userID, projects: projects)
        }
      }
    case .page(let page):
      return await ResultView {
        let user = try request.currentUser()
        return try await (
          user.id,
          database.projects.fetch(user.id, page)
        )
      } onSuccess: { (userID, projects) in
        ProjectsTable(userID: userID, projects: projects)
      }

    case .create(let form):
      return await request.view {
        await ResultView {
          let user = try request.currentUser()
          return try await projectClient.createProject(user.id, form)
        } onSuccess: { response in
          ProjectView(
            projectID: response.projectID,
            activeTab: .rooms,
            completedSteps: response.completedSteps
          ) {
            RoomsView(rooms: response.rooms, sensibleHeatRatio: response.sensibleHeatRatio)
          }
        }
      }

    case .delete(let id):
      return await ResultView {
        try await database.projects.delete(id)
      }

    case .update(let id, let form):
      return await projectView(on: request, projectID: id) {
        _ = try await database.projects.update(id, form)
      }

    case .detail(let projectID, let route):
      switch route {
      case .index:
        return await projectView(on: request, projectID: projectID)
      case .componentLoss(let route):
        return await route.renderView(on: request, projectID: projectID)
      case .ductSizing(let route):
        return await route.renderView(on: request, projectID: projectID)
      case .equipment(let route):
        return await route.renderView(on: request, projectID: projectID)
      case .equivalentLength(let route):
        return await route.renderView(on: request, projectID: projectID)
      case .frictionRate(let route):
        return await route.renderView(on: request, projectID: projectID)
      case .pdf:
        // FIX: This should return a pdf to download or be wrapped in a
        //      result view.
        // return try! await projectClient.toHTML(projectID)
        // This get's handled elsewhere because it returns a response, not a view.
        fatalError()
      case .rooms(let route):
        return await route.renderView(on: request, projectID: projectID)
      }
    }

  }

  func projectView(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    return await request.view {
      await ResultView {
        try await catching()
        guard let project = try await database.projects.get(projectID) else {
          throw NotFoundError()
        }
        return (
          try await database.projects.getCompletedSteps(project.id),
          project
        )
      } onSuccess: { (steps, project) in
        ProjectView(projectID: projectID, activeTab: .project, completedSteps: steps) {
          ProjectDetail(project: project)
        }
      }
    }
  }

}

extension SiteRoute.View.ProjectRoute.EquipmentInfoRoute {
  func renderView(
    on request: ViewController.Request,
    projectID: Project.ID
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    switch self {
    case .index:
      return await equipmentView(on: request, projectID: projectID)

    case .submit(let form):
      return await equipmentView(on: request, projectID: projectID) {
        _ = try await database.equipment.create(form)
      }

    case .update(let id, let updates):
      return await equipmentView(on: request, projectID: projectID) {
        _ = try await database.equipment.update(id, updates)
      }
    }
  }

  func equipmentView(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    return await request.view {
      await ResultView {
        try await catching()
        return (
          try await database.projects.getCompletedSteps(projectID),
          try await database.equipment.fetch(projectID)
        )
      } onSuccess: { (steps, equipment) in
        ProjectView(projectID: projectID, activeTab: .equipment, completedSteps: steps) {
          EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
        }
      }
    }
  }
}

extension SiteRoute.View.ProjectRoute.RoomRoute {
  func renderView(
    on request: ViewController.Request,
    projectID: Project.ID
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    switch self {

    case .delete(let id):
      return await ResultView {
        try await database.rooms.delete(id)
      }

    case .index:
      return await roomsView(on: request, projectID: projectID)

    case .submit(let form):
      return await roomsView(on: request, projectID: projectID) {
        _ = try await database.rooms.create(form)
      }

    case .update(let id, let form):
      return await roomsView(on: request, projectID: projectID) {
        _ = try await database.rooms.update(id, form)
      }

    case .updateSensibleHeatRatio(let form):
      return await roomsView(on: request, projectID: projectID) {
        _ = try await database.projects.update(
          form.projectID,
          .init(sensibleHeatRatio: form.sensibleHeatRatio)
        )
      }
    }
  }

  func roomsView(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    return await request.view {
      await ResultView {
        try await catching()
        return (
          try await database.projects.getCompletedSteps(projectID),
          try await database.rooms.fetch(projectID),
          try await database.projects.getSensibleHeatRatio(projectID)
        )
      } onSuccess: { (steps, rooms, shr) in
        ProjectView(projectID: projectID, activeTab: .rooms, completedSteps: steps) {
          RoomsView(rooms: rooms, sensibleHeatRatio: shr)
        }
      }
    }
  }
}

extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
  func renderView(
    on request: ViewController.Request,
    projectID: Project.ID
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database
    @Dependency(\.manualD) var manualD

    switch self {
    case .index:
      return await view(on: request, projectID: projectID)
    }
  }

  func view(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {

    @Dependency(\.database) var database
    @Dependency(\.manualD) var manualD

    return await request.view {
      await ResultView {
        let equipment = try await database.equipment.fetch(projectID)
        let componentLosses = try await database.componentLoss.fetch(projectID)
        let lengths = try await database.effectiveLength.fetchMax(projectID)

        return (
          try await database.projects.getCompletedSteps(projectID),
          componentLosses,
          lengths,
          try await manualD.frictionRate(
            equipmentInfo: equipment,
            componentLosses: componentLosses,
            effectiveLength: lengths
          )
        )
      } onSuccess: { (steps, losses, lengths, frictionRate) in
        ProjectView(projectID: projectID, activeTab: .frictionRate, completedSteps: steps) {
          FrictionRateView(
            componentLosses: losses,
            equivalentLengths: lengths,
            frictionRate: frictionRate
          )
        }

      }
    }
  }

}

extension SiteRoute.View.ProjectRoute.ComponentLossRoute {

  func renderView(
    on request: ViewController.Request,
    projectID: Project.ID
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    switch self {
    case .index:
      return EmptyHTML()
    case .delete(let id):
      return await view(on: request, projectID: projectID) {
        _ = try await database.componentLoss.delete(id)
      }
    case .submit(let form):
      return await view(on: request, projectID: projectID) {
        _ = try await database.componentLoss.create(form)
      }

    case .update(let id, let form):
      return await view(on: request, projectID: projectID) {
        _ = try await database.componentLoss.update(id, form)
      }
    }
  }

  func view(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {

    @Dependency(\.database) var database
    @Dependency(\.projectClient) var projectClient

    return await request.view {
      await ResultView {
        try await catching()
        return (
          try await database.projects.getCompletedSteps(projectID),
          try await projectClient.frictionRate(projectID)
        )
      } onSuccess: { (steps, response) in
        ProjectView(projectID: projectID, activeTab: .frictionRate, completedSteps: steps) {
          FrictionRateView(
            componentLosses: response.componentLosses,
            equivalentLengths: response.equivalentLengths,
            frictionRate: response.frictionRate
          )
        }

      }
    }
  }

}

extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {

  func renderView(
    on request: ViewController.Request,
    projectID: Project.ID
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    switch self {

    case .delete(let id):
      return await ResultView {
        try await database.effectiveLength.delete(id)
      }

    case .index:
      return await self.view(on: request, projectID: projectID)

    case .field(let type, let style):
      switch type {
      case .straightLength:
        return StraightLengthField()
      case .group:
        // FIX:
        return GroupField(style: style ?? .supply)
      }

    case .update(let id, let form):
      return await view(on: request, projectID: projectID) {
        _ = try await database.effectiveLength.update(id, .init(form: form, projectID: projectID))
      }

    case .submit(let step):
      switch step {
      case .one(let stepOne):
        return await ResultView {
          var effectiveLength: EquivalentLength? = nil
          if let id = stepOne.id {
            effectiveLength = try await database.effectiveLength.get(id)
          }
          return effectiveLength
        } onSuccess: { effectiveLength in
          EffectiveLengthForm.StepTwo(
            projectID: projectID,
            stepOne: stepOne,
            effectiveLength: effectiveLength
          )
        }
      case .two(let stepTwo):
        return await ResultView {
          request.logger.debug("ViewController: Got step two...")
          var effectiveLength: EquivalentLength? = nil
          if let id = stepTwo.id {
            effectiveLength = try await database.effectiveLength.get(id)
          }
          return effectiveLength
        } onSuccess: { effectiveLength in
          return EffectiveLengthForm.StepThree(
            projectID: projectID, effectiveLength: effectiveLength, stepTwo: stepTwo
          )
        }
      case .three(let stepThree):
        return await view(on: request, projectID: projectID) {
          _ = try await database.effectiveLength.create(
            .init(form: stepThree, projectID: projectID)
          )
        }

      }
    }

  }

  func view(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database
    return await request.view {
      await ResultView {
        try await catching()
        return (
          try await database.projects.getCompletedSteps(projectID),
          try await database.effectiveLength.fetch(projectID)
        )
      } onSuccess: { (steps, equivalentLengths) in
        ProjectView(projectID: projectID, activeTab: .equivalentLength, completedSteps: steps) {
          EffectiveLengthsView(effectiveLengths: equivalentLengths)
            .environment(ProjectViewValue.$projectID, projectID)
        }
      }
    }
  }
}

extension SiteRoute.View.ProjectRoute.DuctSizingRoute {

  func renderView(
    on request: ViewController.Request,
    projectID: Project.ID
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database
    @Dependency(\.manualD) var manualD
    @Dependency(\.projectClient) var projectClient

    switch self {
    case .index:
      return await view(on: request, projectID: projectID)

    case .deleteRectangularSize(let roomID, let request):
      return await ResultView {
        let room = try await database.rooms.deleteRectangularSize(roomID, request.rectangularSizeID)
        return try await projectClient.calculateRoomDuctSizes(projectID)
          .filter({ $0.roomID == room.id && $0.roomRegister == request.register })
          .first!
      } onSuccess: { room in
        DuctSizingView.RoomRow(room: room)
      }

    case .roomRectangularForm(let roomID, let form):
      return await ResultView {
        let room = try await database.rooms.updateRectangularSize(
          roomID,
          .init(id: form.id ?? .init(), register: form.register, height: form.height)
        )
        return try await projectClient.calculateRoomDuctSizes(projectID)
          .filter({ $0.roomID == room.id && $0.roomRegister == form.register })
          .first!
      } onSuccess: { room in
        DuctSizingView.RoomRow(room: room)
      }

    case .trunk(let route):
      switch route {
      case .delete(let id):
        return await ResultView {
          try await database.trunkSizes.delete(id)
        }
      case .submit(let form):
        return await view(on: request, projectID: projectID) {
          _ = try await database.trunkSizes.create(
            form.toCreate(logger: request.logger)
          )
        }

      case .update(let id, let form):
        return await view(on: request, projectID: projectID) {
          _ = try await database.trunkSizes.update(id, form.toUpdate())
        }
      }
    }
  }

  func view(
    on request: ViewController.Request,
    projectID: Project.ID,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database
    @Dependency(\.projectClient) var project

    return await request.view {
      await ResultView {
        try await catching()
        return (
          try await database.projects.getCompletedSteps(projectID),
          try await project.calculateDuctSizes(projectID)
        )
      } onSuccess: { (steps, ducts) in
        ProjectView(projectID: projectID, activeTab: .ductSizing, completedSteps: steps) {
          DuctSizingView(ductSizes: ducts)
        }
      }
    }
  }
}

extension SiteRoute.View.UserRoute {

  func renderView(on request: ViewController.Request) async -> AnySendableHTML {
    switch self {
    case .profile(let route):
      return await route.renderView(on: request)
    }
  }
}

extension SiteRoute.View.UserRoute.Profile {

  func renderView(
    on request: ViewController.Request
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    switch self {
    case .index:
      return await view(on: request)
    case .submit(let form):
      return await view(on: request) {
        _ = try await database.userProfile.create(form)
      }
    case .update(let id, let updates):
      return await view(on: request) {
        _ = try await database.userProfile.update(id, updates)
      }
    }
  }

  func view(
    on request: ViewController.Request,
    catching: @escaping @Sendable () async throws -> Void = {}
  ) async -> AnySendableHTML {
    @Dependency(\.database) var database

    return await request.view {
      await ResultView {
        try await catching()
        let user = try request.currentUser()
        return (
          user,
          try await database.userProfile.fetch(user.id)
        )
      } onSuccess: { (user, profile) in
        UserView(user: user, profile: profile)
      }
    }
  }
}
