//
//  ValetInteractor.swift
//  PHP Monitor
//
//  Created by Nico Verbruggen on 21/10/2022.
//  Copyright © 2025 Nico Verbruggen. All rights reserved.
//

import Foundation

struct ValetInteractionError: Error {
    /// The command the user should try (and failed).
    var command: String
}

class ValetInteractor {

    // MARK: - Container

    var container: Container

    init(_ container: Container) {
        self.container = container
    }

    // MARK: - Shared Instance

    static var shared = ValetInteractor(App.shared.container)

    public static func useFake() {
        ValetInteractor.shared = FakeValetInteractor(App.shared.container)
    }

    // MARK: - Managing Domains

    public func link(path: String, domain: String) async throws {
        await container.shell.quiet("cd '\(path)' && \(container.paths.valet) link '\(domain)' && valet links")
    }

    public func unlink(site: ValetSite) async throws {
        await container.shell.quiet("valet unlink '\(site.name)'")
    }

    public func proxy(domain: String, proxy: String, secure: Bool) async throws {
        let command = secure
            ? "\(container.paths.valet) proxy \(domain) \(proxy) --secure"
            : "\(container.paths.valet) proxy \(domain) \(proxy)"

        await container.shell.quiet(command)
        await Actions(container).restartNginx()
    }

    public func remove(proxy: ValetProxy) async throws {
        await container.shell.quiet("valet unproxy '\(proxy.domain)'")
    }

    // MARK: - Modifying Domains

    public func toggleSecure(site: ValetSite) async throws {
        // Keep track of the original status (secure or not?)
        let originalSecureStatus = site.secured

        // Keep track of the command we wish to run
        let action = site.secured ? "unsecure" : "secure"

        // Use modernized version of command using domain name
        // This will allow us to secure multiple domains that use the same path
        var command = "sudo \(container.paths.valet) \(action) '\(site.name)' && exit;"

        // For Valet 2, use the old syntax; this has a known issue so Valet 3+ is preferred
        if !Valet.enabled(feature: .isolatedSites) {
            command = "cd '\(site.absolutePath)' && sudo \(container.paths.valet) \(action) && exit;"
        }

        // Run the command
        await container.shell.quiet(command)

        // Check if the secured status has actually changed
        site.determineSecured()
        if site.secured == originalSecureStatus {
            throw ValetInteractionError(command: command)
        }
    }

    public func toggleSecure(proxy: ValetProxy) async throws {
        // Keep track of the original status (secure or not?)
        let originalSecureStatus = proxy.secured

        // Build the list of commands we will need to run
        let commands: [String] = [
            // Unproxy the given domain
            "\(container.paths.valet) unproxy \(proxy.domain)",
            // Re-create the proxy (with the inverse secured status)
            originalSecureStatus
                ? "\(container.paths.valet) proxy \(proxy.domain) \(proxy.target)"
                : "\(container.paths.valet) proxy \(proxy.domain) \(proxy.target) --secure"
        ]

        // Run the commands
        for command in commands {
            await container.shell.quiet(command)
        }

        // Check if the secured status has actually changed
        proxy.determineSecured()
        if proxy.secured == originalSecureStatus {
            throw ValetInteractionError(
                command: commands.joined(separator: " && ")
            )
        }

        // Restart nginx to load the new configuration
        await Actions(container).restartNginx()
    }

    public func isolate(site: ValetSite, version: String) async throws {
        let command = "sudo \(container.paths.valet) isolate php@\(version) --site '\(site.name)'"

        // Run the command
        await container.shell.quiet(command)

        // Check if the secured status has actually changed
        site.determineIsolated()
        site.determineComposerPhpVersion()

        // If the version is not isolated, this failed
        if site.isolatedPhpVersion == nil {
            throw ValetInteractionError(command: command)
        }
    }

    public func unisolate(site: ValetSite) async throws {
        let command = "sudo \(container.paths.valet) unisolate --site '\(site.name)'"

        // Run the command
        await container.shell.quiet(command)

        // Check if the secured status has actually changed
        site.determineIsolated()
        site.determineComposerPhpVersion()

        // If the version is somehow still isolated, this failed
        if site.isolatedPhpVersion != nil {
            throw ValetInteractionError(command: command)
        }
    }
}
