[CmdletBinding()]
param(
    [string]$BaseUrl = 'http://127.0.0.1/CCW',
    [string]$Username = 'admin',
    [string]$Password = 'admin123',
    [switch]$CheckDuplicateSubmit = $true,
    [switch]$KeepTestData
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

function Get-FlashText {
    param([string]$Html)

    $match = [regex]::Match($Html, '<div class="flash[^"]*">\s*(.*?)\s*</div>', 'IgnoreCase, Singleline')
    if (-not $match.Success) {
        return ''
    }

    $text = [regex]::Replace($match.Groups[1].Value, '<[^>]+>', '')
    return [System.Net.WebUtility]::HtmlDecode($text).Trim()
}

function Get-FormState {
    param(
        [string]$Html,
        [string]$ActionContains,
        [hashtable]$RequiredHidden = @{}
    )

    $forms = [regex]::Matches($Html, '<form\b[^>]*action="([^"]*)"[^>]*>(.*?)</form>', 'IgnoreCase, Singleline')
    foreach ($form in $forms) {
        $action = [System.Net.WebUtility]::HtmlDecode($form.Groups[1].Value)
        $body = $form.Groups[2].Value
        if (-not $action.Contains($ActionContains)) {
            continue
        }

        $matchesRequired = $true
        foreach ($key in $RequiredHidden.Keys) {
            $pattern = '<input\b[^>]*name="' + [regex]::Escape([string]$key) + '"[^>]*value="' + [regex]::Escape([string]$RequiredHidden[$key]) + '"[^>]*>'
            if (-not [regex]::IsMatch($body, $pattern, 'IgnoreCase')) {
                $matchesRequired = $false
                break
            }
        }

        if (-not $matchesRequired) {
            continue
        }

        $state = @{}
        $hiddenInputs = [regex]::Matches($body, '<input\b[^>]*type="hidden"[^>]*name="([^"]+)"[^>]*value="([^"]*)"', 'IgnoreCase')
        foreach ($input in $hiddenInputs) {
            $name = [System.Net.WebUtility]::HtmlDecode($input.Groups[1].Value)
            $value = [System.Net.WebUtility]::HtmlDecode($input.Groups[2].Value)
            $state[$name] = $value
        }

        return $state
    }

    throw "Form not found for action: $ActionContains"
}

function Get-FirstSelectValue {
    param(
        [string]$Html,
        [string]$Name
    )

    $selectPattern = '<select\b[^>]*name="' + [regex]::Escape($Name) + '"[^>]*>(.*?)</select>'
    $selectMatch = [regex]::Match($Html, $selectPattern, 'IgnoreCase, Singleline')
    if (-not $selectMatch.Success) {
        throw "Select not found: $Name"
    }

    $optionMatches = [regex]::Matches($selectMatch.Groups[1].Value, '<option\b[^>]*value="([^"]*)"[^>]*>', 'IgnoreCase')
    foreach ($option in $optionMatches) {
        $value = [System.Net.WebUtility]::HtmlDecode($option.Groups[1].Value).Trim()
        if ($value -ne '') {
            return $value
        }
    }

    throw "No non-empty option found for select: $Name"
}

function Get-FirstServiceId {
    param([string]$Html)

    $match = [regex]::Match($Html, 'name="service_ids\[\]"[^>]*value="(\d+)"', 'IgnoreCase')
    if (-not $match.Success) {
        throw 'No service id found on booking page.'
    }

    return $match.Groups[1].Value
}

function Get-OrderIdFromResponse {
    param($Response)

    $uri = $Response.BaseResponse.ResponseUri.AbsoluteUri
    $match = [regex]::Match($uri, '[?&]id=(\d+)')
    if (-not $match.Success) {
        throw "Order id not found in redirect URL: $uri"
    }

    return [int]$match.Groups[1].Value
}

function Invoke-FlowRequest {
    param(
        [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [string]$Uri,
        [string]$Method = 'GET',
        [hashtable]$Body = @{}
    )

    $params = @{
        Uri = $Uri
        Method = $Method
        WebSession = $Session
        UseBasicParsing = $true
    }

    if ($Method -eq 'POST') {
        $params.Body = $Body
    }

    return Invoke-WebRequest @params
}

function Assert-NotBlank {
    param(
        [string]$Value,
        [string]$Label
    )

    if ([string]::IsNullOrWhiteSpace($Value)) {
        throw "$Label flash was blank."
    }
}

$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
$phone = '099' + $stamp.Substring($stamp.Length - 7)
$plate = 'SM' + $stamp.Substring($stamp.Length - 6)
$results = [ordered]@{}
$createdOrderId = 0
$reopenedOrderId = 0
$cancelledQueueReturnTo = 'queue.php?status=cancelled'

try {
    $loginPage = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/pages/login.php"
    $loginForm = Get-FormState -Html $loginPage.Content -ActionContains '../process/login.php'
    $null = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/login.php" -Method 'POST' -Body @{
        csrf_token = $loginForm['csrf_token']
        username = $Username
        password = $Password
        display_mode = 'app'
    }

    $bookingPage = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/pages/booking.php"
    $bookingForm = Get-FormState -Html $bookingPage.Content -ActionContains '../process/save_booking.php'
    $branchName = Get-FirstSelectValue -Html $bookingPage.Content -Name 'branch_name'
    $serviceId = Get-FirstServiceId -Html $bookingPage.Content

    $bookingResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/save_booking.php" -Method 'POST' -Body @{
        csrf_token = $bookingForm['csrf_token']
        customer_name = "Smoke Flow $stamp"
        phone = $phone
        plate_number = $plate
        province = 'Bangkok'
        vehicle_type = ''
        brand = 'Mazda'
        model = '3'
        color = 'White'
        branch_name = $branchName
        'service_ids[]' = $serviceId
        total_price = ''
        notes = 'smoke order flow'
    }
    $createdOrderId = Get-OrderIdFromResponse -Response $bookingResult
    $results['booking_flash'] = Get-FlashText -Html $bookingResult.Content
    Assert-NotBlank -Value $results['booking_flash'] -Label 'Booking'

    $jobInfoForm = Get-FormState -Html $bookingResult.Content -ActionContains '../process/update_job_info.php'
    $jobInfoResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_info.php" -Method 'POST' -Body @{
        csrf_token = $jobInfoForm['csrf_token']
        order_id = $createdOrderId
        phone = $phone
        plate_number = $plate
        province = 'Bangkok'
        vehicle_type = ''
        brand = 'Mazda'
        model = '3'
        color = 'White'
        branch_name = $branchName
        scheduled_at_date = ''
        scheduled_at_time = ''
        slide_send_at_date = ''
        slide_send_at_time = ''
        service_at_date = ''
        service_at_time = ''
        return_at_date = ''
        return_at_time = ''
        scheduled_at = ''
        slide_send_at = ''
        service_at = ''
        return_at = ''
    }
    $results['job_info_flash'] = Get-FlashText -Html $jobInfoResult.Content
    Assert-NotBlank -Value $results['job_info_flash'] -Label 'Job info update'

    $jobServicesForm = Get-FormState -Html $jobInfoResult.Content -ActionContains '../process/update_job_services.php'
    $jobServicesResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_services.php" -Method 'POST' -Body @{
        csrf_token = $jobServicesForm['csrf_token']
        order_id = $createdOrderId
        'service_ids[]' = $serviceId
        total_price = ''
        film_front_service_id = '0'
        film_front_side_service_id = '0'
        film_rear_side_service_id = '0'
        film_rear_service_id = '0'
        film_notes = ''
    }
    $results['job_services_flash'] = Get-FlashText -Html $jobServicesResult.Content
    Assert-NotBlank -Value $results['job_services_flash'] -Label 'Job services update'

    $startForm = Get-FormState -Html $jobServicesResult.Content -ActionContains '../process/update_job_status.php' -RequiredHidden @{
        order_id = $createdOrderId
        status = 'washing'
    }
    $jobDetailsReturnTo = "job_details.php?id=$createdOrderId"
    $startBody = @{
        csrf_token = $startForm['csrf_token']
        submission_token = $startForm['submission_token']
        order_id = $createdOrderId
        status = 'washing'
        return_to = $jobDetailsReturnTo
    }
    $startResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_status.php" -Method 'POST' -Body $startBody
    $results['queue_start_flash'] = Get-FlashText -Html $startResult.Content
    Assert-NotBlank -Value $results['queue_start_flash'] -Label 'Queue start'

    if ($CheckDuplicateSubmit) {
        $duplicateStartResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_status.php" -Method 'POST' -Body $startBody
        $results['duplicate_start_flash'] = Get-FlashText -Html $duplicateStartResult.Content
        Assert-NotBlank -Value $results['duplicate_start_flash'] -Label 'Duplicate queue start'
    }

    $doneForm = Get-FormState -Html $startResult.Content -ActionContains '../process/update_job_status.php' -RequiredHidden @{
        order_id = $createdOrderId
        status = 'done'
    }
    $doneBody = @{
        csrf_token = $doneForm['csrf_token']
        submission_token = $doneForm['submission_token']
        order_id = $createdOrderId
        status = 'done'
        return_to = $jobDetailsReturnTo
    }
    $doneResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_status.php" -Method 'POST' -Body $doneBody
    $results['queue_done_flash'] = Get-FlashText -Html $doneResult.Content
    Assert-NotBlank -Value $results['queue_done_flash'] -Label 'Queue done'

    $paymentForm = Get-FormState -Html $doneResult.Content -ActionContains '../process/save_payment.php'
    $paymentBody = @{
        csrf_token = $paymentForm['csrf_token']
        submission_token = $paymentForm['submission_token']
        order_id = $createdOrderId
        payment_method = 'cash'
        received_amount = '99999'
        note = 'smoke order flow'
    }
    $paymentResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/save_payment.php" -Method 'POST' -Body $paymentBody
    $results['payment_flash'] = Get-FlashText -Html $paymentResult.Content
    Assert-NotBlank -Value $results['payment_flash'] -Label 'Payment'

    if ($CheckDuplicateSubmit) {
        $duplicatePaymentResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/save_payment.php" -Method 'POST' -Body $paymentBody
        $results['duplicate_payment_flash'] = Get-FlashText -Html $duplicatePaymentResult.Content
        Assert-NotBlank -Value $results['duplicate_payment_flash'] -Label 'Duplicate payment'
    }

    $cancelPage = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/pages/cancel_bill.php?id=$createdOrderId&return_to=$([uri]::EscapeDataString($cancelledQueueReturnTo))"
    $cancelForm = Get-FormState -Html $cancelPage.Content -ActionContains '../process/cancel_bill.php'
    $cancelBody = @{
        csrf_token = $cancelForm['csrf_token']
        submission_token = $cancelForm['submission_token']
        order_id = $createdOrderId
        cancellation_reason = 'smoke order flow'
        return_to = $cancelledQueueReturnTo
    }
    $cancelResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/cancel_bill.php" -Method 'POST' -Body $cancelBody
    $results['cancel_flash'] = Get-FlashText -Html $cancelResult.Content
    Assert-NotBlank -Value $results['cancel_flash'] -Label 'Cancel'

    $cancelledQueuePage = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/pages/queue.php?status=cancelled"
    $reopenForm = Get-FormState -Html $cancelledQueuePage.Content -ActionContains '../process/update_job_status.php' -RequiredHidden @{
        order_id = $createdOrderId
        status = 'waiting'
    }
    $reopenBody = @{
        csrf_token = $reopenForm['csrf_token']
        submission_token = $reopenForm['submission_token']
        order_id = $createdOrderId
        status = 'waiting'
        return_to = ''
    }
    $reopenResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_status.php" -Method 'POST' -Body $reopenBody
    $reopenedOrderId = Get-OrderIdFromResponse -Response $reopenResult
    $results['reopen_flash'] = Get-FlashText -Html $reopenResult.Content
    Assert-NotBlank -Value $results['reopen_flash'] -Label 'Reopen'

    if ($CheckDuplicateSubmit) {
        $duplicateReopenResult = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/update_job_status.php" -Method 'POST' -Body $reopenBody
        $results['duplicate_reopen_flash'] = Get-FlashText -Html $duplicateReopenResult.Content
        Assert-NotBlank -Value $results['duplicate_reopen_flash'] -Label 'Duplicate reopen'
    }

    $results['created_order_id'] = $createdOrderId
    $results['reopened_order_id'] = $reopenedOrderId

    if (-not $KeepTestData -and $reopenedOrderId -gt 0) {
        $cleanupCancelPage = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/pages/cancel_bill.php?id=$reopenedOrderId&return_to=$([uri]::EscapeDataString($cancelledQueueReturnTo))"
        $cleanupForm = Get-FormState -Html $cleanupCancelPage.Content -ActionContains '../process/cancel_bill.php'
        $null = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/cancel_bill.php" -Method 'POST' -Body @{
            csrf_token = $cleanupForm['csrf_token']
            submission_token = $cleanupForm['submission_token']
            order_id = $reopenedOrderId
            cancellation_reason = 'smoke order flow cleanup'
            return_to = $cancelledQueueReturnTo
        }
        $results['cleanup'] = "Cancelled reopened order $reopenedOrderId"
    }

    $results.GetEnumerator() | ForEach-Object {
        "{0}: {1}" -f $_.Key, $_.Value
    }
} catch {
    if (-not $KeepTestData -and $reopenedOrderId -gt 0) {
        try {
            $cleanupCancelPage = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/pages/cancel_bill.php?id=$reopenedOrderId&return_to=$([uri]::EscapeDataString($cancelledQueueReturnTo))"
            $cleanupForm = Get-FormState -Html $cleanupCancelPage.Content -ActionContains '../process/cancel_bill.php'
            $null = Invoke-FlowRequest -Session $session -Uri "$BaseUrl/process/cancel_bill.php" -Method 'POST' -Body @{
                csrf_token = $cleanupForm['csrf_token']
                submission_token = $cleanupForm['submission_token']
                order_id = $reopenedOrderId
                cancellation_reason = 'smoke order flow cleanup'
                return_to = $cancelledQueueReturnTo
            }
        } catch {
        }
    }

    throw
}
