// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.

package source

import (
_	"context"
_	"encoding/json"
_	"fmt"
_	"gopkg.in/yaml.v3"
	"net/url"
	"regexp"
_	"strings"
	"os"
	"io"
	"compress/gzip"
	"archive/tar"
	"errors"
	"path/filepath"
	"decl/internal/resource"
)

type ResourceSelector func(r resource.Resource) bool

type DocSource interface {
	Type() string

	ExtractResources(filter ResourceSelector) ([]*resource.Document, error)
}

func NewDocSource(uri string) DocSource {
	s, e := SourceTypes.New(uri)
	if e == nil {
		return s
	}
	return nil
}

func ExtractResources(uri string, filter ResourceSelector) ([]*resource.Document, error) {
	documents := make([]*resource.Document, 0, 100)
	d := resource.NewDocument()
	documents = append(documents, d)
	
	TarGzipFileName := regexp.MustCompile(`^.*\.(tar\.gz|tgz)$`)
	TarFileName := regexp.MustCompile(`^.*\.tar$`)

	u,e := url.Parse(uri)
	if e != nil {
		return nil, e
	}
	switch u.Scheme {
	case "file":
		fileAbsolutePath, _ := filepath.Abs(filepath.Join(u.Hostname(), u.RequestURI()))
		file, fileErr := os.Open(fileAbsolutePath)
		if fileErr != nil {
			return documents, fileErr
		}
		var gzipReader io.Reader
		switch u.Path {
		case TarGzipFileName.FindString(u.Path):
			zr, err := gzip.NewReader(file)
			if err != nil {
				return documents, err
			}
			gzipReader = zr
			fallthrough
		case TarFileName.FindString(u.Path):
			var fileReader io.Reader
			if gzipReader == nil {
				fileReader = file
			} else {
				fileReader = gzipReader
			}
			tarReader := tar.NewReader(fileReader)
			for {
				hdr, err := tarReader.Next()
				if err == io.EOF {
					break
				}
				if err != nil {
					return documents, err
				}
				f := resource.NewFile()
				if fiErr := f.UpdateAttributesFromFileInfo(hdr.FileInfo()); fiErr != nil {
					return documents, fiErr
				}
				readFileData, readErr := io.ReadAll(tarReader)
				if readErr != nil {
					return documents, readErr
				}
				f.Content = string(readFileData)
				d.AddResourceDeclaration("file", f)
			}
		default:
      decoder := resource.NewYAMLDecoder(file)
      index := 0
			for {
				doc := documents[index]
				e := decoder.Decode(doc)
				if errors.Is(e, io.EOF) {
					if len(documents) > 1 {
						documents[index] = nil
					}
					break
				}
				if e != nil {
					return documents, e
				}
				if validationErr := doc.Validate(); validationErr != nil {
					return documents, validationErr
				}
				if applyErr := doc.Apply(); applyErr != nil {
					return documents, applyErr
				}
				documents = append(documents, resource.NewDocument())
				index++
			}

		}
	}
	return documents, nil
}
