name: CI

on:
  push:
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

permissions:
  contents: read
  checks: write
  pull-requests: write

jobs:
  lint:
    name: Lint (Python 3.11)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"
          cache-dependency-path: "pyproject.toml"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip setuptools wheel
          pip install -e ".[dev]"

      - name: Ruff check
        run: ruff check src tests scripts

      - name: Ruff format
        run: ruff format --check src tests scripts

  test:
    name: Test (Python ${{ matrix.python-version }})
    runs-on: ubuntu-latest
    timeout-minutes: 30
    needs: lint
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: "pip"
          cache-dependency-path: "pyproject.toml"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip setuptools wheel
          pip install -e ".[dev]"

      - name: Run pytest with coverage
        env:
          MPLBACKEND: Agg
          PYTHONPATH: ${{ github.workspace }}/src
        run: |
          pytest \
            --cov=foodspec \
            --cov-report=term-missing \
            --cov-report=xml \
            --cov-report=html \
            --junitxml=junit-${{ matrix.python-version }}.xml \
            --tb=short \
            tests

      - name: Upload coverage to Codecov
        if: matrix.python-version == '3.11'
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml
          flags: unittests
          name: codecov-umbrella
          fail_ci_if_error: false

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ matrix.python-version }}
          path: junit-${{ matrix.python-version }}.xml

  packaging:
    name: Packaging & Installation (Python 3.11)
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"
          cache-dependency-path: "pyproject.toml"

      - name: Install build tools
        run: |
          python -m pip install --upgrade pip setuptools wheel
          pip install build twine

      - name: Build sdist and wheel
        run: python -m build

      - name: Check package metadata
        run: twine check dist/*

      - name: Test import and version
        run: |
          pip install dist/foodspec-*.whl
          python -c "import foodspec; print(f'foodspec {foodspec.__version__}')"

  docs:
    name: Docs (MkDocs, Python 3.11)
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"
          cache-dependency-path: "pyproject.toml"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[docs]"

      - name: Build MkDocs site (strict mode)
        env:
          PYTHONPATH: ${{ github.workspace }}/src
        run: |
          mkdocs build --clean --strict --site-dir site 2>&1 | tee mkdocs-build.log
          exit_code=${PIPESTATUS[0]}
          echo "mkdocs_exit_code=$exit_code" >> $GITHUB_ENV
          exit $exit_code

      - name: Check for broken internal links (post-build validation)
        run: |
          python << 'EOF'
          import os
          import re
          from pathlib import Path
          
          site_dir = Path("site").resolve()
          errors = []
          
          # Extract all links from generated HTML
          for html_file in site_dir.rglob("*.html"):
              # Skip 404.html as it contains absolute paths with /foodspec/ prefix
              if html_file.name == "404.html":
                  continue
              
              content = html_file.read_text()
              # Find href="..." patterns (both relative and absolute)
              for match in re.finditer(r'href=["\']([^"\']+)["\']', content):
                  href = match.group(1)
                  # Skip external links, anchors-only, and special protocols
                  if href.startswith(("http://", "https://", "mailto:", "#", "javascript:")):
                      continue
                  
                  # Strip anchor/fragment for file existence check
                  href_no_anchor = href.split('#')[0]
                  if not href_no_anchor:  # Was just an anchor
                      continue
                  
                  # Resolve relative path
                  if href_no_anchor.startswith("/"):
                      target = site_dir / href_no_anchor.lstrip("/")
                  else:
                      target = html_file.parent / href_no_anchor
                  
                  # Normalize and check existence (file or directory)
                  target = target.resolve()
                  if not target.exists():
                      # Report paths relative to the site root
                      try:
                          resolved_rel = target.relative_to(site_dir)
                      except Exception:
                          resolved_rel = target
                      errors.append(f"{html_file.relative_to(site_dir)}: link to {href} (resolved to {resolved_rel})")
          
          if errors:
              print(f"ERROR: Found {len(errors)} broken internal links:")
              for error in sorted(errors)[:20]:  # Show first 20
                  print(f"  {error}")
              exit(1)
          else:
              print("✓ All internal links are valid")
              exit(0)
          EOF

      - name: Upload docs artifact
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: mkdocs-site
          path: site

      - name: Upload build logs
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: mkdocs-build-logs
          path: mkdocs-build.log

  cli-smoke-test:
    name: CLI Smoke Test (Python 3.11)
    runs-on: ubuntu-latest
    timeout-minutes: 15
    needs: packaging
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"
          cache-dependency-path: "pyproject.toml"

      - name: Install package
        run: |
          python -m pip install --upgrade pip
          pip install -e "."

      - name: Test CLI entry points
        run: |
          echo "Testing CLI entry points..."
          
          # Test main help command
          foodspec --help || exit 1
          echo "✓ foodspec --help"
          
          # Test other CLI scripts exist
          foodspec-registry --help || exit 1
          echo "✓ foodspec-registry --help"
          
          foodspec-plugin --help || exit 1
          echo "✓ foodspec-plugin --help"
          
          # Test version info
          foodspec --version || exit 1
          echo "✓ foodspec --version"
          
          echo "All CLI smoke tests passed!"

  all-checks-pass:
    name: All Checks Pass
    runs-on: ubuntu-latest
    timeout-minutes: 5
    needs: [lint, test, packaging, docs, cli-smoke-test]
    if: always()
    steps:
      - name: Decide whether all checks passed
        run: |
          if [[ "${{ needs.lint.result }}" != "success" ]]; then echo "Lint failed"; exit 1; fi
          if [[ "${{ needs.test.result }}" != "success" ]]; then echo "Tests failed"; exit 1; fi
          if [[ "${{ needs.packaging.result }}" != "success" ]]; then echo "Packaging failed"; exit 1; fi
          if [[ "${{ needs.docs.result }}" != "success" ]]; then echo "Docs failed"; exit 1; fi
          if [[ "${{ needs.cli-smoke-test.result }}" != "success" ]]; then echo "CLI smoke test failed"; exit 1; fi
          echo "All checks passed!"
