package gen

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path"
	"text/template"
	"time"

	"github.com/ghodss/yaml"
	"github.com/pkg/errors"
	"github.com/swaggo/swag"
)

// Gen presents a generate tool for swag.
type Gen struct {
}

// New creates a new Gen.
func New() *Gen {
	return &Gen{}
}

// Config presents Gen configurations.
type Config struct {
	// SearchDir the swag would be parse
	SearchDir string

	//OutputDir represents the output directory for al the generated files
	OutputDir string

	//MainAPIFile the Go file path in which 'swagger general API Info' is written
	MainAPIFile string

	//PropNamingStrategy represents property naming strategy like snakecase,camelcase,pascalcase
	PropNamingStrategy string

	//ParseVendor whether swag should be parse vendor folder
	ParseVendor bool
}

// Build builds swagger json file  for gived searchDir and mainAPIFile. Returns json
func (g *Gen) Build(config *Config) error {
	if _, err := os.Stat(config.SearchDir); os.IsNotExist(err) {
		return fmt.Errorf("dir: %s is not exist", config.SearchDir)
	}

	log.Println("Generate swagger docs....")
	p := swag.New()
	p.PropNamingStrategy = config.PropNamingStrategy
	p.ParseVendor = config.ParseVendor

	if err := p.ParseAPI(config.SearchDir, config.MainAPIFile); err != nil {
		return err
	}
	swagger := p.GetSwagger()

	b, err := json.MarshalIndent(swagger, "", "    ")
	if err != nil {
		return err
	}

	os.MkdirAll(config.OutputDir, os.ModePerm)
	docs, err := os.Create(path.Join(config.OutputDir, "docs.go"))
	if err != nil {
		return err
	}
	defer docs.Close()

	swaggerJSON, err := os.Create(path.Join(config.OutputDir, "swagger.json"))
	if err != nil {
		return err
	}

	defer swaggerJSON.Close()
	swaggerJSON.Write(b)

	swaggerYAML, err := os.Create(path.Join(config.OutputDir, "swagger.yaml"))
	if err != nil {
		return err
	}

	defer swaggerYAML.Close()
	y, err := yaml.JSONToYAML(b)
	if err != nil {
		return errors.Wrap(err, "cannot covert json to yaml")
	}

	swaggerYAML.Write(y)

	if err := packageTemplate.Execute(docs, struct {
		Timestamp time.Time
		Doc       string
	}{
		Timestamp: time.Now(),
		Doc:       "`" + string(b) + "`",
	}); err != nil {
		return err
	}

	log.Printf("create docs.go at  %+v", docs.Name())
	log.Printf("create swagger.json at  %+v", swaggerJSON.Name())
	log.Printf("create swagger.yaml at  %+v", swaggerYAML.Name())

	return nil
}

var packageTemplate = template.Must(template.New("").Parse(`// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// {{ .Timestamp }}

package docs

import (
	"bytes"

	"github.com/alecthomas/template"
	"github.com/swaggo/swag"
)

var doc = {{.Doc}}

type swaggerInfo struct {
	Version     string
	Host        string
	BasePath    string
	Title       string
	Description string
}

// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo swaggerInfo

type s struct{}

func (s *s) ReadDoc() string {
	t, err := template.New("swagger_info").Parse(doc)
	if err != nil {
		return doc
	}

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, SwaggerInfo); err != nil {
		return doc
	}

	return tpl.String()
}

func init() {
	swag.Register(swag.Name, &s{})
}
`))
