package dashboard

import (
	"crypto/rand"
	"fmt"
	"math/big"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/dbaas"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

func toDatabaseResponse(d dbaas.Database) dto.DatabaseResponse {
	resp := dto.DatabaseResponse{
		ID:        d.ID,
		Name:      d.Name,
		ServiceID: d.ServiceID,
		Provider:  d.Provider,
		Vendor:    d.Vendor,
		Region:    d.Region,
		Status:    string(d.Status),
		Host:      d.Host,
		Port:      d.Port,
		DBName:    d.DBName,
		Username:  d.Username,
		DSN:       d.DSN,
	}
	if d.CreatedAt != nil {
		s := d.CreatedAt.UTC().Format(time.RFC3339)
		resp.CreatedAt = &s
	}
	return resp
}

// ---------------------------------------------------------------------------
// GET /databases
// ---------------------------------------------------------------------------

func NewListDatabasesHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		databases := profile.Default().Databases().All()
		resp := make([]dto.DatabaseResponse, 0, len(databases))
		for _, d := range databases {
			resp = append(resp, toDatabaseResponse(d))
		}
		c.JSON(http.StatusOK, resp)
	}
}

// ---------------------------------------------------------------------------
// POST /databases
// ---------------------------------------------------------------------------

func NewCreateDatabaseHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		var req dto.DatabaseCreateRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid request body: %v", err)})
			return
		}

		// Validate the service exists
		svc, ok := profile.Default().DBaaS().Get(req.ServiceID)
		if !ok {
			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("data service %q not found", req.ServiceID)})
			return
		}

		now := time.Now()
		db := dbaas.Database{
			Name:      req.Name,
			ServiceID: req.ServiceID,
			Provider:  svc.Provider,
			Vendor:    req.Vendor,
			Region:    req.Region,
			Status:    dbaas.DatabaseStatusRunning,
			CreatedAt: &now,
		}

		// Mock: create a SQLite instance locally
		mockSQLiteInstance(&db)

		if err := profile.Default().Databases().Add(db); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		if stored, ok := profile.Default().Databases().Get(db.ID); ok {
			c.JSON(http.StatusCreated, toDatabaseResponse(stored))
			return
		}
		c.JSON(http.StatusCreated, toDatabaseResponse(db))
	}
}

// mockSQLiteInstance populates mock connection info for a SQLite instance.
func mockSQLiteInstance(db *dbaas.Database) {
	if db.Name == "" {
		db.Name = fmt.Sprintf("db-%s-%s", db.Vendor, db.Region)
	}
	db.Host = "127.0.0.1"
	db.Port = 0
	db.DBName = db.Name + ".db"
	db.Username = "root"
	db.Password = generateMockPassword()
	db.DSN = fmt.Sprintf("sqlite://%s", db.DBName)
}

// generateMockPassword creates a random 16-char password for mock instances.
func generateMockPassword() string {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, 16)
	for i := range b {
		n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		b[i] = charset[n.Int64()]
	}
	return string(b)
}

// ---------------------------------------------------------------------------
// GET /databases/:id
// ---------------------------------------------------------------------------

func NewGetDatabaseHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		id := c.Param("id")
		d, ok := profile.Default().Databases().Get(id)
		if !ok {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("database %q not found", id)})
			return
		}
		c.JSON(http.StatusOK, toDatabaseResponse(d))
	}
}

// ---------------------------------------------------------------------------
// PATCH /databases/:id
// ---------------------------------------------------------------------------

func NewUpdateDatabaseHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		id := c.Param("id")
		d, ok := profile.Default().Databases().Get(id)
		if !ok {
			c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("database %q not found", id)})
			return
		}

		var req dto.DatabaseUpdateRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid request body: %v", err)})
			return
		}

		if req.Username != nil {
			d.Username = *req.Username
		}
		if req.Password != nil {
			d.Password = *req.Password
		}

		if err := profile.Default().Databases().Update(d); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		c.JSON(http.StatusOK, toDatabaseResponse(d))
	}
}

// ---------------------------------------------------------------------------
// DELETE /databases/:id
// ---------------------------------------------------------------------------

func NewDeleteDatabaseHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		id := c.Param("id")
		if err := profile.Default().Databases().Delete(id); err != nil {
			c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": "deleted", "id": id})
	}
}

// ---------------------------------------------------------------------------
// GET /databases/vendors
// ---------------------------------------------------------------------------

func NewListVendorsHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		type vendorInfo struct {
			ID      string   `json:"id"`
			Regions []string `json:"regions"`
		}
		vendors := make([]vendorInfo, 0, len(dbaas.AllVendors))
		for _, v := range dbaas.AllVendors {
			vendors = append(vendors, vendorInfo{
				ID:      v,
				Regions: dbaas.VendorRegions[v],
			})
		}
		c.JSON(http.StatusOK, vendors)
	}
}
