package admin

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"

	"github.com/veylant/ia-gateway/internal/apierror"
	"github.com/veylant/ia-gateway/internal/middleware"
)

// User represents a managed user stored in the users table.
type User struct {
	ID         string    `json:"id"`
	TenantID   string    `json:"tenant_id"`
	Email      string    `json:"email"`
	Name       string    `json:"name"`
	Department string    `json:"department"`
	Role       string    `json:"role"`
	IsActive   bool      `json:"is_active"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

type createUserRequest struct {
	Email      string `json:"email"`
	Name       string `json:"name"`
	Department string `json:"department"`
	Role       string `json:"role"`
	IsActive   *bool  `json:"is_active"`
	Password   string `json:"password"` // optional; bcrypt-hashed before storage
}

// userStore wraps a *sql.DB to perform user CRUD operations.
type userStore struct {
	db     *sql.DB
	logger *zap.Logger
}

func newUserStore(db *sql.DB, logger *zap.Logger) *userStore {
	return &userStore{db: db, logger: logger}
}

func (s *userStore) list(tenantID string) ([]User, error) {
	rows, err := s.db.Query(
		`SELECT id, tenant_id, email, name, COALESCE(department,''), role, is_active, created_at, updated_at
		 FROM users WHERE tenant_id = $1 ORDER BY created_at DESC`, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var users []User
	for rows.Next() {
		var u User
		if err := rows.Scan(&u.ID, &u.TenantID, &u.Email, &u.Name, &u.Department,
			&u.Role, &u.IsActive, &u.CreatedAt, &u.UpdatedAt); err != nil {
			return nil, err
		}
		users = append(users, u)
	}
	return users, rows.Err()
}

func (s *userStore) get(id, tenantID string) (*User, error) {
	var u User
	err := s.db.QueryRow(
		`SELECT id, tenant_id, email, name, COALESCE(department,''), role, is_active, created_at, updated_at
		 FROM users WHERE id = $1 AND tenant_id = $2`, id, tenantID,
	).Scan(&u.ID, &u.TenantID, &u.Email, &u.Name, &u.Department,
		&u.Role, &u.IsActive, &u.CreatedAt, &u.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	return &u, err
}

func (s *userStore) create(u User, passwordHash string) (*User, error) {
	var created User
	err := s.db.QueryRow(
		`INSERT INTO users (tenant_id, email, name, department, role, is_active, password_hash)
		 VALUES ($1,$2,$3,$4,$5,$6,$7)
		 RETURNING id, tenant_id, email, name, COALESCE(department,''), role, is_active, created_at, updated_at`,
		u.TenantID, u.Email, u.Name, u.Department, u.Role, u.IsActive, passwordHash,
	).Scan(&created.ID, &created.TenantID, &created.Email, &created.Name, &created.Department,
		&created.Role, &created.IsActive, &created.CreatedAt, &created.UpdatedAt)
	return &created, err
}

func (s *userStore) update(u User, passwordHash string) (*User, error) {
	var updated User
	var err error
	if passwordHash != "" {
		err = s.db.QueryRow(
			`UPDATE users SET email=$1, name=$2, department=$3, role=$4, is_active=$5, password_hash=$6, updated_at=NOW()
			 WHERE id=$7 AND tenant_id=$8
			 RETURNING id, tenant_id, email, name, COALESCE(department,''), role, is_active, created_at, updated_at`,
			u.Email, u.Name, u.Department, u.Role, u.IsActive, passwordHash, u.ID, u.TenantID,
		).Scan(&updated.ID, &updated.TenantID, &updated.Email, &updated.Name, &updated.Department,
			&updated.Role, &updated.IsActive, &updated.CreatedAt, &updated.UpdatedAt)
	} else {
		err = s.db.QueryRow(
			`UPDATE users SET email=$1, name=$2, department=$3, role=$4, is_active=$5, updated_at=NOW()
			 WHERE id=$6 AND tenant_id=$7
			 RETURNING id, tenant_id, email, name, COALESCE(department,''), role, is_active, created_at, updated_at`,
			u.Email, u.Name, u.Department, u.Role, u.IsActive, u.ID, u.TenantID,
		).Scan(&updated.ID, &updated.TenantID, &updated.Email, &updated.Name, &updated.Department,
			&updated.Role, &updated.IsActive, &updated.CreatedAt, &updated.UpdatedAt)
	}
	if err == sql.ErrNoRows {
		return nil, nil
	}
	return &updated, err
}

func (s *userStore) delete(id, tenantID string) error {
	res, err := s.db.Exec(
		`DELETE FROM users WHERE id = $1 AND tenant_id = $2`, id, tenantID)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return sql.ErrNoRows
	}
	return nil
}

// ─── HTTP handlers ────────────────────────────────────────────────────────────

func (h *Handler) listUsers(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{
			Type:       "not_implemented",
			Message:    "database not configured",
			HTTPStatus: http.StatusNotImplemented,
		})
		return
	}
	us := newUserStore(h.db, h.logger)
	users, err := us.list(tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to list users: "+err.Error()))
		return
	}
	if users == nil {
		users = []User{}
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"data": users})
}

func (h *Handler) createUser(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}

	var req createUserRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}
	if req.Email == "" || req.Name == "" {
		apierror.WriteError(w, apierror.NewBadRequestError("email and name are required"))
		return
	}

	role := req.Role
	if role == "" {
		role = "user"
	}
	isActive := true
	if req.IsActive != nil {
		isActive = *req.IsActive
	}

	passwordHash := ""
	if req.Password != "" {
		hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
		if err != nil {
			apierror.WriteError(w, apierror.NewUpstreamError("failed to hash password"))
			return
		}
		passwordHash = string(hash)
	}

	us := newUserStore(h.db, h.logger)
	created, err := us.create(User{
		TenantID:   tenantID,
		Email:      req.Email,
		Name:       req.Name,
		Department: req.Department,
		Role:       role,
		IsActive:   isActive,
	}, passwordHash)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to create user: "+err.Error()))
		return
	}
	writeJSON(w, http.StatusCreated, created)
}

func (h *Handler) getUser(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	us := newUserStore(h.db, h.logger)
	u, err := us.get(id, tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
		return
	}
	if u == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_found_error", Message: "user not found", HTTPStatus: http.StatusNotFound})
		return
	}
	writeJSON(w, http.StatusOK, u)
}

func (h *Handler) updateUser(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}

	var req createUserRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}
	isActive := true
	if req.IsActive != nil {
		isActive = *req.IsActive
	}

	passwordHash := ""
	if req.Password != "" {
		hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
		if err != nil {
			apierror.WriteError(w, apierror.NewUpstreamError("failed to hash password"))
			return
		}
		passwordHash = string(hash)
	}

	us := newUserStore(h.db, h.logger)
	updated, err := us.update(User{
		ID:         id,
		TenantID:   tenantID,
		Email:      req.Email,
		Name:       req.Name,
		Department: req.Department,
		Role:       req.Role,
		IsActive:   isActive,
	}, passwordHash)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
		return
	}
	if updated == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_found_error", Message: "user not found", HTTPStatus: http.StatusNotFound})
		return
	}
	writeJSON(w, http.StatusOK, updated)
}

func (h *Handler) deleteUser(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFromCtx(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "database not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	us := newUserStore(h.db, h.logger)
	if err := us.delete(id, tenantID); err != nil {
		if err == sql.ErrNoRows {
			apierror.WriteError(w, &apierror.APIError{Type: "not_found_error", Message: "user not found", HTTPStatus: http.StatusNotFound})
			return
		}
		apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *Handler) getProviderStatus(w http.ResponseWriter, r *http.Request) {
	if h.router == nil {
		apierror.WriteError(w, &apierror.APIError{Type: "not_implemented", Message: "provider router not configured", HTTPStatus: http.StatusNotImplemented})
		return
	}
	statuses := h.router.ProviderStatuses()

	// Also call health check for each provider (E2-10).
	healthCtx := r.Context()
	type providerStatusResponse struct {
		Provider string `json:"provider"`
		State    string `json:"state"`
		Failures int    `json:"failures"`
		OpenedAt string `json:"opened_at,omitempty"`
		Healthy  *bool  `json:"healthy,omitempty"`
	}
	_ = healthCtx // suppress unused warning; health ping is async in production

	writeJSON(w, http.StatusOK, statuses)
}

// tenantFromMiddlewareCtx is an alias kept for consistency.
func tenantFromMiddlewareCtx(r *http.Request) (string, bool) {
	claims, ok := middleware.ClaimsFromContext(r.Context())
	if !ok || claims.TenantID == "" {
		return "", false
	}
	return claims.TenantID, true
}
