// SPDX-License-Identifier: MIT
//
// Real-world ecosystem fixtures for Kubernetes, GitHub Actions, and Ansible.
//
// These tests complement the yaml-test-suite conformance suite with patterns
// specific to YAML usage in the wild. Each fixture verifies:
//   1. No false-positive diagnostics on valid YAML
//   2. Specific bug regressions: duplicate key false positives,
//      empty flow collection warnings

#![expect(missing_docs, reason = "test code")]

use rlsp_yaml::parser::parse_yaml;
use rlsp_yaml::validation::ValidationSettings;
use rlsp_yaml::validation::validators::{validate_duplicate_keys, validate_flow_style};
// ---- Helpers ----------------------------------------------------------------

/// Assert that the given valid YAML text produces no duplicate-key false-positive
/// diagnostics. Flow-style false positives are eliminated structurally by the
/// AST-based validator; empty collections never warn and plain scalars containing
/// `{`/`[` (e.g. `${{ … }}`) are never flagged.
fn assert_no_false_positives(label: &str, text: &str) {
    let docs = parse_yaml(text).documents;
    let dup_diags = validate_duplicate_keys(&docs, &ValidationSettings::default());
    assert!(
        dup_diags.is_empty(),
        "{label}: unexpected duplicate-key diagnostics: {dup_diags:?}"
    );
}

// ---- Kubernetes fixtures ----------------------------------------------------

/// K8s `LimitRange` — the duplicate-key bug pattern.
/// `cpu` and `memory` appear under multiple sibling mappings (`max`, `min`,
/// `default`, `defaultRequest`). The validator must not flag these as
/// duplicates.
const K8S_LIMIT_RANGE: &str = "\
apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-memory-limits
  namespace: default
spec:
  limits:
    - type: Container
      max:
        cpu: \"2\"
        memory: 1Gi
      min:
        cpu: 100m
        memory: 128Mi
      default:
        cpu: 500m
        memory: 256Mi
      defaultRequest:
        cpu: 200m
        memory: 128Mi
";

/// K8s Deployment — flow sequence in containers/command, and `status: {}`.
const K8S_DEPLOYMENT: &str = "\
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: python:3.11
          command: [\"python\", \"-m\", \"http.server\", \"5000\"]
          ports:
            - containerPort: 5000
          resources:
            limits:
              cpu: 500m
              memory: 256Mi
            requests:
              cpu: 100m
              memory: 128Mi
status: {}
";

/// K8s `ConfigMap` — simple key/value data (no block scalars, which are a known
/// formatter limitation tracked by the conformance suite baseline).
const K8S_CONFIG_MAP: &str = "\
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  server_port: \"8080\"
  server_host: 0.0.0.0
  app_name: MyApp
  debug: \"false\"
";

/// K8s Service — `status: {}` is idiomatic and must not warn.
const K8S_SERVICE: &str = "\
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: default
spec:
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000
  type: ClusterIP
status: {}
";

// ---- GitHub Actions fixtures ------------------------------------------------

/// GitHub Actions workflow — `on:` key must stay unquoted, flow sequences in
/// `branches`, blank lines between sections.
///
/// Note: block scalar `run: |` steps are intentionally omitted here — block
/// scalar indentation is a known formatter limitation tracked by the
/// conformance suite baseline (51 "formatted output unparseable" cases).
const GHA_WORKFLOW: &str = "\
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Run tests
        run: cargo test --workspace
      - name: Check format
        run: cargo fmt --check
";

/// GitHub Actions matrix strategy — flow mapping syntax.
const GHA_MATRIX: &str = "\
name: Matrix

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        rust: [stable, beta]
    steps:
      - uses: actions/checkout@v6
      - name: Build
        run: cargo build
";

/// GitHub Actions workflow representative of `.github/workflows/release-plz.yml` —
/// multiple `${{ … }}` expressions in env/if/run alongside a real flow mapping
/// in `strategy.matrix`. This is the regression fixture for the GHA false-positive
/// bug class: expression lines must produce zero diagnostics; the matrix line must
/// produce exactly one `flowMap` diagnostic.
const GHA_RELEASE_PLZ_STYLE: &str = "\
name: Release

on:
  push:
    branches: [main]

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    if: ${{ github.ref == 'refs/heads/main' }}
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
      - name: Run release-plz
        run: |
          echo token=${{ secrets.GITHUB_TOKEN }}

  cross-build:
    name: Cross Build
    runs-on: ubuntu-latest
    strategy:
      matrix: { target: x86_64-unknown-linux-musl, os: ubuntu-latest }
    steps:
      - uses: actions/checkout@v4
      - name: Build
        env:
          MATRIX_JSON: ${{ fromJSON(needs.prepare.outputs.config) }}
        run: cargo build --target ${{ matrix.target }}
";

// ---- Ansible fixtures -------------------------------------------------------

/// Ansible playbook — `yes`/`no` and `on`/`off` YAML 1.1 keywords as values.
const ANSIBLE_PLAYBOOK: &str = "\
---
- name: Configure web server
  hosts: webservers
  become: yes
  gather_facts: yes
  vars:
    app_enabled: yes
    debug_mode: no
    service_started: on
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: Start nginx
      service:
        name: nginx
        state: started
        enabled: yes
";

// ---- Kubernetes tests -------------------------------------------------------

#[test]
fn k8s_limit_range_no_duplicate_key_false_positives() {
    assert_no_false_positives("K8s LimitRange", K8S_LIMIT_RANGE);
}

#[test]
fn k8s_deployment_no_false_positives() {
    assert_no_false_positives("K8s Deployment", K8S_DEPLOYMENT);
}

#[test]
fn k8s_deployment_status_empty_flow_map_no_warning() {
    // `status: {}` must not produce a flowMap warning — AST knows the collection is empty.
    let docs = parse_yaml(K8S_DEPLOYMENT).documents;
    let diags = validate_flow_style(&docs, &ValidationSettings::default());
    let status_warnings: Vec<_> = diags
        .iter()
        .filter(|d| {
            matches!(d.code.as_ref(), Some(tower_lsp::lsp_types::NumberOrString::String(code)) if code == "flowMap")
        })
        .collect();
    assert!(
        status_warnings.is_empty(),
        "status: {{}} should not warn: {status_warnings:?}"
    );
}

#[test]
fn k8s_config_map_no_false_positives() {
    assert_no_false_positives("K8s ConfigMap", K8S_CONFIG_MAP);
}

#[test]
fn k8s_service_status_empty_flow_map_no_warning() {
    // `status: {}` on a Service must not produce a flowMap warning — AST knows the collection is empty.
    let docs = parse_yaml(K8S_SERVICE).documents;
    let diags = validate_flow_style(&docs, &ValidationSettings::default());
    let status_warnings: Vec<_> = diags
        .iter()
        .filter(|d| {
            matches!(d.code.as_ref(), Some(tower_lsp::lsp_types::NumberOrString::String(code)) if code == "flowMap")
        })
        .collect();
    assert!(
        status_warnings.is_empty(),
        "status: {{}} on Service should not warn: {status_warnings:?}"
    );
}

// ---- GitHub Actions tests ---------------------------------------------------

#[test]
fn gha_workflow_no_false_positives() {
    assert_no_false_positives("GHA Workflow", GHA_WORKFLOW);
}

#[test]
fn gha_matrix_no_false_positives() {
    assert_no_false_positives("GHA Matrix", GHA_MATRIX);
}

#[test]
fn gha_release_plz_style_no_false_positives() {
    assert_no_false_positives("GHA release-plz style", GHA_RELEASE_PLZ_STYLE);
}

#[test]
fn gha_release_plz_style_expression_lines_zero_flow_diagnostics() {
    // `${{ … }}` expressions must never produce flowMap/flowSeq diagnostics.
    // The fixture has exactly two real flow collections:
    //   - `branches: [main]`                      → flowSeq
    //   - `matrix: { target: …, os: … }`          → flowMap
    // All `${{ … }}` expression lines must produce zero additional diagnostics.
    let docs = parse_yaml(GHA_RELEASE_PLZ_STYLE).documents;
    let diags = validate_flow_style(&docs, &ValidationSettings::default());

    let flow_diags: Vec<_> = diags
        .iter()
        .filter(|d| {
            matches!(
                d.code.as_ref(),
                Some(tower_lsp::lsp_types::NumberOrString::String(code))
                    if code == "flowMap" || code == "flowSeq"
            )
        })
        .collect();

    // Exactly two diagnostics: branches flow seq + matrix flow map.
    assert_eq!(
        flow_diags.len(),
        2,
        "expected 2 flow diagnostics (branches flowSeq + matrix flowMap), got: {flow_diags:?}"
    );
    let has_flow_seq = flow_diags.iter().any(|d| {
        matches!(d.code.as_ref(), Some(tower_lsp::lsp_types::NumberOrString::String(code)) if code == "flowSeq")
    });
    let has_flow_map = flow_diags.iter().any(|d| {
        matches!(d.code.as_ref(), Some(tower_lsp::lsp_types::NumberOrString::String(code)) if code == "flowMap")
    });
    assert!(
        has_flow_seq,
        "expected flowSeq diagnostic for branches: [main]"
    );
    assert!(
        has_flow_map,
        "expected flowMap diagnostic for matrix: {{ … }}"
    );
}

// ---- Ansible tests ----------------------------------------------------------

#[test]
fn ansible_playbook_no_false_positives() {
    assert_no_false_positives("Ansible Playbook", ANSIBLE_PLAYBOOK);
}

#[test]
fn ansible_yaml11_keywords_preserved_unquoted() {
    // `yes`, `no`, `on`, `off` are YAML 1.1 booleans used as plain scalars.
    // The formatter must preserve them as-is (they are plain strings in YAML 1.2).
    let result = parse_yaml(ANSIBLE_PLAYBOOK);
    assert!(
        result.diagnostics.is_empty(),
        "Ansible playbook should parse cleanly: {:?}",
        result.diagnostics
    );
}

// ---- Cross-fixture regression tests -----------------------------------------

#[test]
fn all_fixtures_parse_cleanly() {
    // None of the valid ecosystem fixtures should produce parse-level errors.
    let fixtures = [
        ("K8s LimitRange", K8S_LIMIT_RANGE),
        ("K8s Deployment", K8S_DEPLOYMENT),
        ("K8s ConfigMap", K8S_CONFIG_MAP),
        ("K8s Service", K8S_SERVICE),
        ("GHA Workflow", GHA_WORKFLOW),
        ("GHA Matrix", GHA_MATRIX),
        ("GHA release-plz style", GHA_RELEASE_PLZ_STYLE),
        ("Ansible Playbook", ANSIBLE_PLAYBOOK),
    ];
    for (label, text) in &fixtures {
        let result = parse_yaml(text);
        assert!(
            result.diagnostics.is_empty(),
            "{label}: unexpected parse diagnostics: {:?}",
            result.diagnostics
        );
    }
}
