import Hummingbird
import Logging
import NIOPosix

struct DocCMiddleware<
    Context: RequestContext,
    AssetProvider: FileProvider
>: RouterMiddleware {
    
    // MARK: Properties
    
    private let assetProvider: AssetProvider
    
    // MARK: Initialisers
    
    init(
        _ rootFolder: String,
        threadPool: NIOThreadPool = .singleton,
        logger: Logger = .init(label: "DocCMiddleware")
    ) where AssetProvider == LocalFileSystem {
        self.assetProvider = LocalFileSystem(
            rootFolder: rootFolder,
            threadPool: threadPool,
            logger: logger
        )
    }

    init(assetProvider: AssetProvider) {
        self.assetProvider = assetProvider
    }
    
    // MARK: Functions
    
    func handle(
        _ input: Request,
        context: Context,
        next: (Request, Context) async throws -> Response
    ) async throws -> Response {
        guard
            let uriPath = input.uri.path.removingPercentEncoding,
            !uriPath.contains(.previousFolder),
            uriPath.hasPrefix(.forwardSlash)
        else {
            throw HTTPError(.badRequest)
        }
        
        /*
        Send all requests starting with /documentation/ or /tutorials/ to the index.html file
        Send all requests starting with /css/, /data/, /downloads/, /images/, /img/, /index/, /js/, or /videos/ to their respective folders
        Send all requests to favicon.ico, favicon.svg, and theme-settings.json to their respective files
        Send all requests to /data/documentation.json to the file in the data/documentation/ folder that has the name of the module and ends with .json
        Redirect requests to / and /documentation to /documentation/
        Redirect requests to /tutorials to /tutorials/
        */

        guard uriPath.starts(with: /^\/archives\/\w+/) else {
            return try await next(input, context)
        }
        
        let pathArchive = Path.archivePath(from: uriPath)
        let nameArchive = Path.archiveName(from: uriPath)
        let uriResource = Path.resourcePath(from: uriPath)

        if uriResource == .forwardSlash {
            return .redirect(to: uriPath + "/documentation")
        }

        for staticFile in StaticFile.allCases {
            if uriResource.contains(staticFile.path) {
                if staticFile == .documentation {
                    // Send all requests to /data/documentation.json to the file in the data/documentation/ folder that has the name of the module and ends with .json
                    return try await serveFile(
                        "/data/documentation/\(nameArchive).json",
                        at: pathArchive,
                        context: context
                    )
                } else {
                    // Send all requests to favicon.ico, favicon.svg, and theme-settings.json to their respective files
                    return try await serveFile(
                        uriResource,
                        at: pathArchive,
                        context: context
                    )
                }
            }
        }
        
        for assetPrefix in AssetPrefix.allCases {
            if uriResource.contains(assetPrefix.path) {
                return try await serveFile(
                    uriResource,
                    at: pathArchive,
                    context: context
                )
            }
        }
        
        for indexPrefix in IndexPrefix.allCases {
            if uriResource.contains(indexPrefix.path) {
                if uriResource.hasSuffix(.forwardSlash) {
                    return try await serveFile(
                        "\(indexPrefix.path)/\(nameArchive)/index.html",
                        at: pathArchive,
                        context: context
                    )
                } else {
                    return .redirect(to: uriPath + "/")
                }
            }
        }

        throw HTTPError(.notFound)
    }
    
}

// MARK: - Helpers

private extension DocCMiddleware {
    
    // MARK: Functions
    
    func serveFile(
        _ path: String,
        at folder: String,
        context: Context
    ) async throws -> Response {
        let filePath = folder + path

        guard let fileIdentifier = assetProvider.getFileIdentifier(filePath) else {
            throw HTTPError(.notFound)
        }

        let body = try await assetProvider.loadFile(id: fileIdentifier, context: context)

        return .init(status: .ok, headers: [:], body: body)
    }
    
}
