package gitea

import (
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"git.supported.systems/rsp2k/caddy-gitea"
)

func init() {
	// Register the module and the Caddyfile handler directive.
	caddy.RegisterModule(Middleware{})
	httpcaddyfile.RegisterHandlerDirective("gitea", parseCaddyfile)
}

// parseCaddyfile creates and configures the module from Caddyfile input.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
	var m Middleware
	if err := m.UnmarshalCaddyfile(h.Dispenser); err != nil {
		return nil, err
	}
	return &m, nil
}

// Middleware implements the Gitea plugin.
type Middleware struct {
	// The Gitea client is created during Provision.
	Client *gitea.Client `json:"-"`

	// Required/optional configuration fields:
	Server             string `json:"server,omitempty"`              // e.g. "https://gitea.example.com"
	Token              string `json:"token,omitempty"`               // API token if needed
	GiteaPages         string `json:"gitea_pages,omitempty"`         // e.g. URL for Gitea pages
	GiteaPagesAllowAll string `json:"gitea_pages_allowall,omitempty"`  // configuration for allow-all pages mode
	Domain             string `json:"domain,omitempty"`              // if set, used to split subdomain parts
}

// CaddyModule returns the Caddy module information.
func (Middleware) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID: "http.handlers.gitea",
		New: func() caddy.Module {
			return new(Middleware)
		},
	}
}

// Provision creates the Gitea client.
func (m *Middleware) Provision(ctx caddy.Context) error {
	// Validate required configuration.
	if m.Server == "" {
		return fmt.Errorf("gitea: server must be specified")
	}

	var err error
	m.Client, err = gitea.NewClient(m.Server, m.Token, m.GiteaPages, m.GiteaPagesAllowAll)
	return err
}

// Validate performs additional configuration validation.
func (m *Middleware) Validate() error {
	if m.Server == "" {
		return fmt.Errorf("gitea: server is required")
	}
	return nil
}

// UnmarshalCaddyfile sets up the configuration from Caddyfile tokens.
func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
	// Each block of tokens (if more than one block is given) will be processed.
	for d.Next() {
		// Process all subdirectives in the block.
		for n := d.Nesting(); d.NextBlock(n); {
			switch d.Val() {
			case "server":
				if !d.NextArg() {
					return d.ArgErr()
				}
				m.Server = d.Val()
			case "token":
				if !d.NextArg() {
					return d.ArgErr()
				}
				m.Token = d.Val()
			case "gitea_pages":
				if !d.NextArg() {
					return d.ArgErr()
				}
				m.GiteaPages = d.Val()
			case "gitea_pages_allowall":
				if !d.NextArg() {
					return d.ArgErr()
				}
				m.GiteaPagesAllowAll = d.Val()
			case "domain":
				if !d.NextArg() {
					return d.ArgErr()
				}
				m.Domain = d.Val()
			default:
				return d.Errf("gitea: unrecognized subdirective '%s'", d.Val())
			}
		}
	}
	return nil
}

// ServeHTTP implements the HTTP handler. It uses the Gitea client to
// retrieve content based on the request’s host and path.
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	// Derive the file path from the request's host and URL.
	//
	// If m.Domain is set then we expect requests in one of two forms:
	//   - repo.username.<domain>  (len=2 after splitting by '.')
	//   - branch.repo.username.<domain>  (len=3)
	//
	// Otherwise, we just use the first segment of the host.
	host := r.Host
	if m.Domain != "" {
		// Remove the configured domain (and a trailing dot, if any)
		host = strings.TrimSuffix(host, m.Domain)
		host = strings.TrimRight(host, ".")
	}
	parts := strings.Split(host, ".")
	var fp string
	// Default: use first part plus the URL path.
	if len(parts) > 0 {
		fp = parts[0] + r.URL.Path
	}
	// Adjust based on subdomain count when a domain is configured.
	ref := r.URL.Query().Get("ref")
	if m.Domain != "" {
		switch len(parts) {
		case 2:
			// Format: repo.username.<domain>
			fp = parts[1] + "/" + parts[0] + r.URL.Path
		case 3:
			// Format: branch.repo.username.<domain>
			fp = parts[2] + "/" + parts[1] + r.URL.Path
			ref = parts[0]
		}
	}

	// Open the file from Gitea.
	f, err := m.Client.Open(fp, ref)
	if err != nil {
		// Return a 404 error if the file isn’t found.
		return caddyhttp.Error(http.StatusNotFound, err)
	}
	// If the returned file supports closing, ensure it gets closed.
	if closer, ok := f.(io.Closer); ok {
		defer closer.Close()
	}

	// Copy the file's contents to the response.
	_, err = io.Copy(w, f)
	return err
}

// Interface guards
var (
	_ caddy.Provisioner           = (*Middleware)(nil)
	_ caddy.Validator             = (*Middleware)(nil)
	_ caddyhttp.MiddlewareHandler = (*Middleware)(nil)
	_ caddyfile.Unmarshaler       = (*Middleware)(nil)
)