package schema

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.yaml.in/yaml/v3"
)

func TestAdditionalFunctions(t *testing.T) {
	var node yaml.Node
	err := yaml.Unmarshal([]byte(`
on: push
jobs:
  job-with-condition:
    runs-on: self-hosted
    if: success() || success('joba', 'jobb') || failure() || failure('joba', 'jobb') || always() || cancelled()
    steps:
    - run: exit 0
`), &node)
	if !assert.NoError(t, err) {
		return
	}
	err = (&Node{
		Definition: "workflow-root-strict",
		Schema:     GetWorkflowSchema(),
	}).UnmarshalYAML(&node)
	assert.NoError(t, err)
}

func TestReusableWorkflow(t *testing.T) {
	t.Run("KnownContexts", func(t *testing.T) {
		var node yaml.Node
		err := yaml.Unmarshal([]byte(`
on: push

jobs:
  job:
    uses: ./.forgejo/workflow/test.yaml
    with:
      input1: |
         ${{ forge.KEY }}
         ${{ github.KEY }}
         ${{ inputs.KEY }}
         ${{ vars.KEY }}
         ${{ env.KEY }}
         ${{ needs.KEY }}
         ${{ strategy.KEY }}
         ${{ matrix.KEY }}
`), &node)
		if !assert.NoError(t, err) {
			return
		}
		err = (&Node{
			Definition: "workflow-root",
			Schema:     GetWorkflowSchema(),
		}).UnmarshalYAML(&node)
		assert.NoError(t, err)
	})

	t.Run("UnknownContext", func(t *testing.T) {
		for _, context := range []string{"secrets", "job", "steps", "runner"} {
			t.Run(context, func(t *testing.T) {
				var node yaml.Node
				err := yaml.Unmarshal([]byte(fmt.Sprintf(`
on: push

jobs:
  job:
    uses: ./.forgejo/workflow/test.yaml
    with:
      input1: ${{ %[1]s.KEY }}
`, context)), &node)
				if !assert.NoError(t, err) {
					return
				}
				err = (&Node{
					Definition: "workflow-root",
					Schema:     GetWorkflowSchema(),
				}).UnmarshalYAML(&node)
				assert.ErrorContains(t, err, "Unknown Variable Access "+context)
			})
		}
	})
}

func TestAdditionalFunctionsFailure(t *testing.T) {
	var node yaml.Node
	err := yaml.Unmarshal([]byte(`
on: push
jobs:
  job-with-condition:
    runs-on: self-hosted
    if: success() || success('joba', 'jobb') || failure() || failure('joba', 'jobb') || always('error')
    steps:
    - run: exit 0
`), &node)
	if !assert.NoError(t, err) {
		return
	}
	err = (&Node{
		Definition: "workflow-root-strict",
		Schema:     GetWorkflowSchema(),
	}).UnmarshalYAML(&node)
	assert.Error(t, err)
}

func TestAdditionalFunctionsSteps(t *testing.T) {
	var node yaml.Node
	err := yaml.Unmarshal([]byte(`
on: push
jobs:
  job-with-condition:
    runs-on: self-hosted
    steps:
    - run: exit 0
      if: success() || failure() || always()
    - uses: https://${{ secrets.PAT }}@example.com/action/here@v1
`), &node)
	if !assert.NoError(t, err) {
		return
	}
	err = (&Node{
		Definition: "workflow-root-strict",
		Schema:     GetWorkflowSchema(),
	}).UnmarshalYAML(&node)
	assert.NoError(t, err)
}

func TestAdditionalFunctionsStepsExprSyntax(t *testing.T) {
	var node yaml.Node
	err := yaml.Unmarshal([]byte(`
on: push
jobs:
  job-with-condition:
    runs-on: self-hosted
    steps:
    - run: exit 0
      if: ${{ success() || failure() || always() }}
`), &node)
	if !assert.NoError(t, err) {
		return
	}
	err = (&Node{
		Definition: "workflow-root-strict",
		Schema:     GetWorkflowSchema(),
	}).UnmarshalYAML(&node)
	assert.NoError(t, err)
}

func TestWorkflowCallRunsOn(t *testing.T) {
	var node yaml.Node
	err := yaml.Unmarshal([]byte(`
name: Build Silo Frontend DEV
on:
  push:
    branches:
      - dev
      - dev-*
jobs:
  build_frontend_dev:
    name: Build Silo Frontend DEV
    runs-on: ubuntu-latest
    container:
      image: code.forgejo.org/oci/node:22-bookworm
    uses: ./.forgejo/workflows/${{ vars.PATHNAME }}
    with:
      STAGE: dev
    secrets:
      PACKAGE_WRITER_TOKEN: ${{ secrets.PACKAGE_WRITER_TOKEN }}
`), &node)
	require.NoError(t, err)
	n := &Node{
		Definition: "workflow-root",
		Schema:     GetWorkflowSchema(),
	}
	require.NoError(t, n.UnmarshalYAML(&node))
}

func TestActionSchema(t *testing.T) {
	for _, testCase := range []struct {
		name   string
		action string
	}{
		{
			name: "Expressions",
			action: `
name: 'action name'
author: 'action authors'
description: |
  action ${{ env.SOMETHING }} description
inputs:
  url:
    description: 'url description'
    default: '${{ env.GITHUB_SERVER_URL }}'
  repo:
    description: 'repo description'
    default: '${{ github.repository }} ${{ vars.VARIABLE }} ${{ inputs.VARIABLE }}'
runs:
  using: "composite"
  steps:
    - run: |
        echo "${{ github.action_path }}"
      env:
          MYVAR: ${{ vars.VARIABLE }}
`,
		},
		{
			name: "NoInputs",
			action: `
runs:
  using: "composite"
  steps:
    - run: echo OK
`,
		},
		{
			name: "NoMappingInputs",
			action: `
inputs:
  parameter1:
  parameter2:
runs:
  using: "composite"
  steps:
    - run: echo OK
`,
		},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			var node yaml.Node
			err := yaml.Unmarshal([]byte(testCase.action), &node)
			if !assert.NoError(t, err) {
				return
			}
			err = (&Node{
				Definition: "action-root",
				Schema:     GetActionSchema(),
			}).UnmarshalYAML(&node)
			assert.NoError(t, err)
		})
	}
}

// https://yaml.org/spec/1.2.1/#id2785586
// An anchor is denoted by the “&” indicator. It marks a node for future reference.
// https://yaml.org/type/merge.html
// Specify one or more mappings to be merged with the current one.
func TestSchema_AnchorAndReference(t *testing.T) {
	var node yaml.Node
	err := yaml.Unmarshal([]byte(`
on: [push]
jobs:
  test1:
    runs-on: docker
    steps:
      - &step
        run: echo All good!
      - *step
  test2:
    runs-on: docker
    steps:
      - << : *step
        name: other name
  test3:
    runs-on: docker
    steps:
      - !!merge << : *step
        name: other name
`), &node)
	if !assert.NoError(t, err) {
		return
	}
	err = (&Node{
		Definition: "workflow-root",
		Schema:     GetWorkflowSchema(),
	}).UnmarshalYAML(&node)
	assert.NoError(t, err)
}
