[CmdletBinding()]
param(
    [string]$BaseUrl = 'http://127.0.0.1/CCW',
    [string]$Username = 'admin',
    [string]$Password = 'admin123',
    [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 @{
            Action = $action
            Html = $body
            State = $state
        }
    }

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

function Get-InputValue {
    param(
        [string]$FormHtml,
        [string]$Name
    )

    $pattern = '<input\b[^>]*name="' + [regex]::Escape($Name) + '"[^>]*value="([^"]*)"'
    $match = [regex]::Match($FormHtml, $pattern, 'IgnoreCase')
    if (-not $match.Success) {
        return ''
    }

    return [System.Net.WebUtility]::HtmlDecode($match.Groups[1].Value)
}

function Get-SelectSelectedValue {
    param(
        [string]$FormHtml,
        [string]$Name
    )

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

    $selectedMatch = [regex]::Match($selectMatch.Groups[1].Value, '<option\b[^>]*value="([^"]*)"[^>]*selected[^>]*>', 'IgnoreCase')
    if ($selectedMatch.Success) {
        return [System.Net.WebUtility]::HtmlDecode($selectedMatch.Groups[1].Value)
    }

    return ''
}

function Get-CheckedValues {
    param(
        [string]$FormHtml,
        [string]$Name
    )

    $values = New-Object System.Collections.Generic.List[string]
    $pattern = '<input\b[^>]*name="' + [regex]::Escape($Name) + '"[^>]*value="([^"]*)"[^>]*checked[^>]*>'
    $matches = [regex]::Matches($FormHtml, $pattern, 'IgnoreCase')
    foreach ($match in $matches) {
        $values.Add([System.Net.WebUtility]::HtmlDecode($match.Groups[1].Value)) | Out-Null
    }

    return $values.ToArray()
}

function Get-SelectOptions {
    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"
    }

    $options = New-Object System.Collections.Generic.List[object]
    $optionMatches = [regex]::Matches($selectMatch.Groups[1].Value, '<option\b[^>]*value="([^"]*)"[^>]*>(.*?)</option>', 'IgnoreCase, Singleline')
    foreach ($option in $optionMatches) {
        $value = [System.Net.WebUtility]::HtmlDecode($option.Groups[1].Value).Trim()
        $label = [regex]::Replace($option.Groups[2].Value, '<[^>]+>', '')
        $label = [System.Net.WebUtility]::HtmlDecode($label).Trim()
        if ($value -eq '') {
            continue
        }

        $options.Add([pscustomobject]@{
            Value = $value
            Label = $label
        }) | Out-Null
    }

    return $options.ToArray()
}

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 Get-BillNumberFromText {
    param([string]$Text)

    $match = [regex]::Match($Text, '\b[A-Z]{3}\d{6}/\d+\b')
    if (-not $match.Success) {
        throw "Bill number not found in text: $Text"
    }

    return $match.Value
}

function Find-EmployeeEditHref {
    param(
        [string]$Html,
        [string]$Username
    )

    $rows = [regex]::Matches($Html, '<tr>(.*?)</tr>', 'IgnoreCase, Singleline')
    foreach ($row in $rows) {
        $rowHtml = $row.Groups[1].Value
        if ($rowHtml -notmatch [regex]::Escape($Username)) {
            continue
        }

        $editMatch = [regex]::Match($rowHtml, '<a\b[^>]*href="([^"]*employees\.php\?employee_id=\d+[^"]*)"', 'IgnoreCase')
        if ($editMatch.Success) {
            return [System.Net.WebUtility]::HtmlDecode($editMatch.Groups[1].Value)
        }
    }

    throw "Employee edit link not found for username: $Username"
}

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 was blank."
    }
}

function Assert-ContainsText {
    param(
        [string]$Html,
        [string]$ExpectedText,
        [string]$Label
    )

    if ($Html -notmatch [regex]::Escape($ExpectedText)) {
        throw "$Label did not contain expected text: $ExpectedText"
    }
}

function Assert-NotContainsText {
    param(
        [string]$Html,
        [string]$UnexpectedText,
        [string]$Label
    )

    if ($Html -match [regex]::Escape($UnexpectedText)) {
        throw "$Label unexpectedly contained text: $UnexpectedText"
    }
}

function Login-Session {
    param(
        [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [string]$BaseUrl,
        [string]$Username,
        [string]$Password
    )

    $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.State['csrf_token']
        username = $Username
        password = $Password
        display_mode = 'app'
    }
}

function New-BookingOrder {
    param(
        [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [string]$BaseUrl,
        [string]$BranchName,
        [string]$ServiceId,
        [string]$CustomerLabel,
        [string]$Phone,
        [string]$Plate,
        [string]$Notes
    )

    $bookingPage = Invoke-FlowRequest -Session $Session -Uri "$BaseUrl/pages/booking.php"
    $bookingForm = Get-FormState -Html $bookingPage.Content -ActionContains '../process/save_booking.php'
    $bookingResult = Invoke-FlowRequest -Session $Session -Uri "$BaseUrl/process/save_booking.php" -Method 'POST' -Body @{
        csrf_token = $bookingForm.State['csrf_token']
        customer_name = $CustomerLabel
        phone = $Phone
        plate_number = $Plate
        province = 'Bangkok'
        vehicle_type = ''
        brand = 'Mazda'
        model = '3'
        color = 'White'
        branch_name = $BranchName
        'service_ids[]' = $ServiceId
        total_price = ''
        notes = $Notes
    }

    $flash = Get-FlashText -Html $bookingResult.Content
    Assert-NotBlank -Value $flash -Label "Booking flash [$BranchName]"

    return @{
        Response = $bookingResult
        Flash = $flash
        OrderId = (Get-OrderIdFromResponse -Response $bookingResult)
        BillNumber = (Get-BillNumberFromText -Text $flash)
    }
}

function Cancel-Order {
    param(
        [Microsoft.PowerShell.Commands.WebRequestSession]$Session,
        [string]$BaseUrl,
        [int]$OrderId,
        [string]$Reason = 'branch scope smoke cleanup'
    )

    if ($OrderId -le 0) {
        return ''
    }

    $returnTo = 'queue.php?status=cancelled'
    $cancelPage = Invoke-FlowRequest -Session $Session -Uri "$BaseUrl/pages/cancel_bill.php?id=$OrderId&return_to=$([uri]::EscapeDataString($returnTo))"
    $cancelForm = Get-FormState -Html $cancelPage.Content -ActionContains '../process/cancel_bill.php'
    $cancelResult = Invoke-FlowRequest -Session $Session -Uri "$BaseUrl/process/cancel_bill.php" -Method 'POST' -Body @{
        csrf_token = $cancelForm.State['csrf_token']
        submission_token = $cancelForm.State['submission_token']
        order_id = $OrderId
        cancellation_reason = $Reason
        return_to = $returnTo
    }

    return Get-FlashText -Html $cancelResult.Content
}

$adminSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$limitedSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
$results = [ordered]@{}
$allowedOrderId = 0
$blockedOrderId = 0
$restrictedOrderId = 0
$restrictedEmployeeId = 0
$restrictedEmployeeUsername = "branch_uat_$stamp"
$restrictedEmployeePassword = "Scope$($stamp.Substring($stamp.Length - 6))!"
$restrictedEmployeeEditHref = ''

try {
    Login-Session -Session $adminSession -BaseUrl $BaseUrl -Username $Username -Password $Password

    $adminBookingPage = Invoke-FlowRequest -Session $adminSession -Uri "$BaseUrl/pages/booking.php"
    $branchOptions = @(Get-SelectOptions -Html $adminBookingPage.Content -Name 'branch_name')
    if ($branchOptions.Count -lt 2) {
        throw 'At least two branches are required for branch scope UAT.'
    }

    $allowedBranch = [string]$branchOptions[0].Value
    $blockedBranch = [string]$branchOptions[1].Value
    $serviceId = Get-FirstServiceId -Html $adminBookingPage.Content

    $results['allowed_branch'] = $allowedBranch
    $results['blocked_branch'] = $blockedBranch

    $allowedSeed = New-BookingOrder -Session $adminSession -BaseUrl $BaseUrl -BranchName $allowedBranch -ServiceId $serviceId -CustomerLabel "Branch Scope Allowed $stamp" -Phone ('081' + $stamp.Substring($stamp.Length - 7)) -Plate ('BA' + $stamp.Substring($stamp.Length - 6)) -Notes 'branch scope allowed seed'
    $allowedOrderId = [int]$allowedSeed.OrderId
    $results['allowed_seed_flash'] = [string]$allowedSeed.Flash
    $results['allowed_seed_order_id'] = $allowedOrderId
    $results['allowed_seed_bill_number'] = [string]$allowedSeed.BillNumber

    $blockedSeed = New-BookingOrder -Session $adminSession -BaseUrl $BaseUrl -BranchName $blockedBranch -ServiceId $serviceId -CustomerLabel "Branch Scope Blocked $stamp" -Phone ('082' + $stamp.Substring($stamp.Length - 7)) -Plate ('BB' + $stamp.Substring($stamp.Length - 6)) -Notes 'branch scope blocked seed'
    $blockedOrderId = [int]$blockedSeed.OrderId
    $results['blocked_seed_flash'] = [string]$blockedSeed.Flash
    $results['blocked_seed_order_id'] = $blockedOrderId
    $results['blocked_seed_bill_number'] = [string]$blockedSeed.BillNumber

    $employeesPage = Invoke-FlowRequest -Session $adminSession -Uri "$BaseUrl/pages/employees.php?show_form=1"
    $employeeForm = Get-FormState -Html $employeesPage.Content -ActionContains '../process/save_employee.php'
    $positionOptions = @(Get-SelectOptions -Html $employeesPage.Content -Name 'position_id')
    $preferredPositionLabels = @('Administrator', 'Branch Manager', 'Service Advisor')
    $selectedPosition = $null
    foreach ($preferredLabel in $preferredPositionLabels) {
        $selectedPosition = $positionOptions | Where-Object { $_.Label -eq $preferredLabel } | Select-Object -First 1
        if ($null -ne $selectedPosition) {
            break
        }
    }
    if ($null -eq $selectedPosition) {
        throw 'No suitable position found for branch scope test employee.'
    }
    $results['restricted_employee_position'] = [string]$selectedPosition.Label

    $employeeCreateResult = Invoke-FlowRequest -Session $adminSession -Uri "$BaseUrl/process/save_employee.php" -Method 'POST' -Body @{
        filter_position_id = $employeeForm.State['filter_position_id']
        name = "Branch Scope UAT $stamp"
        phone = '083' + $stamp.Substring($stamp.Length - 7)
        username = $restrictedEmployeeUsername
        password = $restrictedEmployeePassword
        position_id = [string]$selectedPosition.Value
        branch_scope = 'assigned'
        default_start_page = ''
        is_active = '1'
        'branch_names[]' = $allowedBranch
    }
    $results['employee_create_flash'] = Get-FlashText -Html $employeeCreateResult.Content
    Assert-NotBlank -Value $results['employee_create_flash'] -Label 'Employee create flash'

    $restrictedEmployeeEditHref = Find-EmployeeEditHref -Html $employeeCreateResult.Content -Username $restrictedEmployeeUsername
    $restrictedEmployeeEditPage = Invoke-FlowRequest -Session $adminSession -Uri "$BaseUrl/pages/$restrictedEmployeeEditHref"
    $restrictedEmployeeForm = Get-FormState -Html $restrictedEmployeeEditPage.Content -ActionContains '../process/save_employee.php'
    $restrictedEmployeeId = [int]$restrictedEmployeeForm.State['employee_id']
    $results['restricted_employee_id'] = $restrictedEmployeeId
    $results['restricted_employee_username'] = $restrictedEmployeeUsername

    Login-Session -Session $limitedSession -BaseUrl $BaseUrl -Username $restrictedEmployeeUsername -Password $restrictedEmployeePassword

    $limitedBookingPage = Invoke-FlowRequest -Session $limitedSession -Uri "$BaseUrl/pages/booking.php"
    $limitedBranchOptions = @(Get-SelectOptions -Html $limitedBookingPage.Content -Name 'branch_name')
    $limitedBranchValues = @($limitedBranchOptions | ForEach-Object { [string]$_.Value })
    if ($limitedBranchValues.Count -ne 1 -or $limitedBranchValues[0] -ne $allowedBranch) {
        throw "Restricted employee branch options were unexpected: $($limitedBranchValues -join ', ')"
    }
    $results['restricted_branch_options'] = ($limitedBranchValues -join ', ')

    $limitedBookingForm = Get-FormState -Html $limitedBookingPage.Content -ActionContains '../process/save_booking.php'
    $restrictedServiceId = Get-FirstServiceId -Html $limitedBookingPage.Content
    $allowedBookingResult = Invoke-FlowRequest -Session $limitedSession -Uri "$BaseUrl/process/save_booking.php" -Method 'POST' -Body @{
        csrf_token = $limitedBookingForm.State['csrf_token']
        customer_name = "Restricted Allowed $stamp"
        phone = '084' + $stamp.Substring($stamp.Length - 7)
        plate_number = 'RA' + $stamp.Substring($stamp.Length - 6)
        province = 'Bangkok'
        vehicle_type = ''
        brand = 'Mazda'
        model = '2'
        color = 'Black'
        branch_name = $allowedBranch
        'service_ids[]' = $restrictedServiceId
        total_price = ''
        notes = 'restricted allowed booking'
    }
    $restrictedOrderId = Get-OrderIdFromResponse -Response $allowedBookingResult
    $results['restricted_booking_flash'] = Get-FlashText -Html $allowedBookingResult.Content
    Assert-NotBlank -Value $results['restricted_booking_flash'] -Label 'Restricted allowed booking flash'
    $results['restricted_order_id'] = $restrictedOrderId
    $results['restricted_order_bill_number'] = Get-BillNumberFromText -Text $results['restricted_booking_flash']

    $blockedBookingResult = Invoke-FlowRequest -Session $limitedSession -Uri "$BaseUrl/process/save_booking.php" -Method 'POST' -Body @{
        csrf_token = $limitedBookingForm.State['csrf_token']
        customer_name = "Restricted Blocked $stamp"
        phone = '085' + $stamp.Substring($stamp.Length - 7)
        plate_number = 'RB' + $stamp.Substring($stamp.Length - 6)
        province = 'Bangkok'
        vehicle_type = ''
        brand = 'Mazda'
        model = 'CX-3'
        color = 'Gray'
        branch_name = $blockedBranch
        'service_ids[]' = $restrictedServiceId
        total_price = ''
        notes = 'restricted blocked booking'
    }
    $results['blocked_booking_flash'] = Get-FlashText -Html $blockedBookingResult.Content
    Assert-NotBlank -Value $results['blocked_booking_flash'] -Label 'Restricted blocked booking flash'
    if ($blockedBookingResult.BaseResponse.ResponseUri.AbsoluteUri -notmatch 'booking\.php') {
        throw 'Blocked booking attempt did not return to booking page.'
    }

    $allowedJobPage = Invoke-FlowRequest -Session $limitedSession -Uri "$BaseUrl/pages/job_details.php?id=$allowedOrderId"
    if ($allowedJobPage.StatusCode -ne 200) {
        throw "Allowed job details returned unexpected status: $($allowedJobPage.StatusCode)"
    }
    Assert-ContainsText -Html $allowedJobPage.Content -ExpectedText ([string]$allowedSeed.BillNumber) -Label 'Allowed job details'
    $results['allowed_job_status'] = [string]$allowedJobPage.StatusCode

    $blockedJobPage = Invoke-FlowRequest -Session $limitedSession -Uri "$BaseUrl/pages/job_details.php?id=$blockedOrderId"
    $results['blocked_job_flash'] = Get-FlashText -Html $blockedJobPage.Content
    Assert-NotBlank -Value $results['blocked_job_flash'] -Label 'Blocked job details flash'
    if ($blockedJobPage.BaseResponse.ResponseUri.AbsoluteUri -match "job_details\.php\?id=$blockedOrderId") {
        throw 'Blocked job details unexpectedly remained on the restricted order page.'
    }
    $results['blocked_job_redirect_uri'] = $blockedJobPage.BaseResponse.ResponseUri.AbsoluteUri

    $queuePage = Invoke-FlowRequest -Session $limitedSession -Uri "$BaseUrl/pages/queue.php"
    Assert-ContainsText -Html $queuePage.Content -ExpectedText ([string]$allowedSeed.BillNumber) -Label 'Restricted queue page'
    Assert-NotContainsText -Html $queuePage.Content -UnexpectedText ([string]$blockedSeed.BillNumber) -Label 'Restricted queue page'
    $results['queue_allowed_visible'] = 'true'
    $results['queue_blocked_hidden'] = 'true'

    $results.GetEnumerator() | ForEach-Object {
        "{0}: {1}" -f $_.Key, $_.Value
    }
} catch {
    throw
} finally {
    if (-not $KeepTestData) {
        if ($restrictedOrderId -gt 0) {
            try {
                $cleanupFlash = Cancel-Order -Session $adminSession -BaseUrl $BaseUrl -OrderId $restrictedOrderId -Reason 'branch scope smoke cleanup restricted'
                if (-not [string]::IsNullOrWhiteSpace($cleanupFlash)) {
                    Write-Output ("cleanup_restricted_order_flash: {0}" -f $cleanupFlash)
                }
            } catch {
            }
        }

        if ($allowedOrderId -gt 0) {
            try {
                $cleanupFlash = Cancel-Order -Session $adminSession -BaseUrl $BaseUrl -OrderId $allowedOrderId -Reason 'branch scope smoke cleanup allowed seed'
                if (-not [string]::IsNullOrWhiteSpace($cleanupFlash)) {
                    Write-Output ("cleanup_allowed_seed_flash: {0}" -f $cleanupFlash)
                }
            } catch {
            }
        }

        if ($blockedOrderId -gt 0) {
            try {
                $cleanupFlash = Cancel-Order -Session $adminSession -BaseUrl $BaseUrl -OrderId $blockedOrderId -Reason 'branch scope smoke cleanup blocked seed'
                if (-not [string]::IsNullOrWhiteSpace($cleanupFlash)) {
                    Write-Output ("cleanup_blocked_seed_flash: {0}" -f $cleanupFlash)
                }
            } catch {
            }
        }

        if ($restrictedEmployeeId -gt 0 -and $restrictedEmployeeEditHref -ne '') {
            try {
                $employeeEditPage = Invoke-FlowRequest -Session $adminSession -Uri "$BaseUrl/pages/$restrictedEmployeeEditHref"
                $employeeForm = Get-FormState -Html $employeeEditPage.Content -ActionContains '../process/save_employee.php'
                $employeeFormHtml = $employeeForm.Html
                $deactivateBody = @{
                    employee_id = $employeeForm.State['employee_id']
                    filter_position_id = $employeeForm.State['filter_position_id']
                    name = Get-InputValue -FormHtml $employeeFormHtml -Name 'name'
                    phone = Get-InputValue -FormHtml $employeeFormHtml -Name 'phone'
                    username = Get-InputValue -FormHtml $employeeFormHtml -Name 'username'
                    password = ''
                    position_id = Get-SelectSelectedValue -FormHtml $employeeFormHtml -Name 'position_id'
                    branch_scope = Get-SelectSelectedValue -FormHtml $employeeFormHtml -Name 'branch_scope'
                    default_start_page = Get-SelectSelectedValue -FormHtml $employeeFormHtml -Name 'default_start_page'
                }
                $branchSelections = @(Get-CheckedValues -FormHtml $employeeFormHtml -Name 'branch_names[]')
                if ($branchSelections.Count -gt 0) {
                    $deactivateBody['branch_names[]'] = $branchSelections
                }

                $deactivateResult = Invoke-FlowRequest -Session $adminSession -Uri "$BaseUrl/process/save_employee.php" -Method 'POST' -Body $deactivateBody
                $deactivateFlash = Get-FlashText -Html $deactivateResult.Content
                if (-not [string]::IsNullOrWhiteSpace($deactivateFlash)) {
                    Write-Output ("cleanup_employee_flash: {0}" -f $deactivateFlash)
                }
            } catch {
            }
        }
    }
}
