package profissionais

import (
	"encoding/json"
	"net/http"

	"photum-backend/internal/db/generated"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgtype"
)

type Handler struct {
	service *Service
}

func NewHandler(service *Service) *Handler {
	return &Handler{service: service}
}

// ProfissionalResponse struct for Swagger and JSON response
type ProfissionalResponse struct {
	ID                   string          `json:"id"`
	UsuarioID            string          `json:"usuario_id"`
	Nome                 string          `json:"nome"`
	FuncaoProfissional   string          `json:"funcao_profissional"` // Deprecated single name (optional)
	FuncaoProfissionalID string          `json:"funcao_profissional_id"`
	Functions            json.RawMessage `json:"functions" swaggertype:"array,object"` // JSON array
	Endereco             *string         `json:"endereco"`
	Cidade               *string         `json:"cidade"`
	Uf                   *string         `json:"uf"`
	Cep                  *string         `json:"cep"`
	Numero               *string         `json:"numero"`
	Complemento          *string         `json:"complemento"`
	Bairro               *string         `json:"bairro"`
	Whatsapp             *string         `json:"whatsapp"`
	CpfCnpjTitular       *string         `json:"cpf_cnpj_titular"`
	Banco                *string         `json:"banco"`
	Agencia              *string         `json:"agencia"`
	Conta                *string         `json:"conta"`
	ContaPix             *string         `json:"conta_pix"`
	CarroDisponivel      *bool           `json:"carro_disponivel"`
	TemEstudio           *bool           `json:"tem_estudio"`
	QtdEstudio           *int            `json:"qtd_estudio"`
	TipoCartao           *string         `json:"tipo_cartao"`
	Observacao           *string         `json:"observacao"`
	QualTec              *int            `json:"qual_tec"`
	EducacaoSimpatia     *int            `json:"educacao_simpatia"`
	DesempenhoEvento     *int            `json:"desempenho_evento"`
	DispHorario          *int            `json:"disp_horario"`
	Media                *float64        `json:"media"`
	TabelaFree           *string         `json:"tabela_free"`
	ExtraPorEquipamento  *bool           `json:"extra_por_equipamento"`
	Equipamentos         *string         `json:"equipamentos"`
	Email                *string         `json:"email"`
	AvatarURL            *string         `json:"avatar_url"`
}

func toResponse(p interface{}) ProfissionalResponse {
	// Handle different types returned by queries (Create returns CadastroProfissionai, List/Get returns Row with join)
	// This is a bit hacky, ideally we'd have a unified model or separate response mappers.
	// For now, let's check type.

	switch v := p.(type) {
	case generated.CadastroProfissionai:
		return ProfissionalResponse{
			ID:                   uuid.UUID(v.ID.Bytes).String(),
			UsuarioID:            uuid.UUID(v.UsuarioID.Bytes).String(),
			Nome:                 v.Nome,
			FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(),
			// FuncaoProfissional name is not available in simple insert return without extra query or join
			FuncaoProfissional:  "",
			Functions:           json.RawMessage("[]"), // Empty on Create (or Fetch specifically if needed)
			Endereco:            fromPgText(v.Endereco),
			Cidade:              fromPgText(v.Cidade),
			Uf:                  fromPgText(v.Uf),
			Cep:                 fromPgText(v.Cep),
			Numero:              fromPgText(v.Numero),
			Complemento:         fromPgText(v.Complemento),
			Bairro:              fromPgText(v.Bairro),
			Whatsapp:            fromPgText(v.Whatsapp),
			CpfCnpjTitular:      fromPgText(v.CpfCnpjTitular),
			Banco:               fromPgText(v.Banco),
			Agencia:             fromPgText(v.Agencia),
			Conta:               fromPgText(v.Conta),
			ContaPix:            fromPgText(v.ContaPix),
			CarroDisponivel:     fromPgBool(v.CarroDisponivel),
			TemEstudio:          fromPgBool(v.TemEstudio),
			QtdEstudio:          fromPgInt4(v.QtdEstudio),
			TipoCartao:          fromPgText(v.TipoCartao),
			Observacao:          fromPgText(v.Observacao),
			QualTec:             fromPgInt4(v.QualTec),
			EducacaoSimpatia:    fromPgInt4(v.EducacaoSimpatia),
			DesempenhoEvento:    fromPgInt4(v.DesempenhoEvento),
			DispHorario:         fromPgInt4(v.DispHorario),
			Media:               fromPgNumeric(v.Media),
			TabelaFree:          fromPgText(v.TabelaFree),
			ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento),
			Equipamentos:        fromPgText(v.Equipamentos),
			Email:               fromPgText(v.Email),
			AvatarURL:           fromPgText(v.AvatarUrl),
		}
	case generated.ListProfissionaisRow:
		email := fromPgText(v.Email)
		if email == nil {
			email = fromPgText(v.UsuarioEmail)
		}
		return ProfissionalResponse{
			ID:                   uuid.UUID(v.ID.Bytes).String(),
			UsuarioID:            uuid.UUID(v.UsuarioID.Bytes).String(),
			Nome:                 v.Nome,
			FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(),
			FuncaoProfissional:   "", // v.FuncaoNome removed from query or changed?
			Functions:            toJSONRaw(v.Functions),
			Endereco:             fromPgText(v.Endereco),
			Cidade:               fromPgText(v.Cidade),
			Uf:                   fromPgText(v.Uf),
			Cep:                  fromPgText(v.Cep),
			Numero:               fromPgText(v.Numero),
			Complemento:          fromPgText(v.Complemento),
			Bairro:               fromPgText(v.Bairro),
			Whatsapp:             fromPgText(v.Whatsapp),
			CpfCnpjTitular:       fromPgText(v.CpfCnpjTitular),
			Banco:                fromPgText(v.Banco),
			Agencia:              fromPgText(v.Agencia),
			Conta:                fromPgText(v.Conta),
			ContaPix:             fromPgText(v.ContaPix),
			CarroDisponivel:      fromPgBool(v.CarroDisponivel),
			TemEstudio:           fromPgBool(v.TemEstudio),
			QtdEstudio:           fromPgInt4(v.QtdEstudio),
			TipoCartao:           fromPgText(v.TipoCartao),
			Observacao:           fromPgText(v.Observacao),
			QualTec:              fromPgInt4(v.QualTec),
			EducacaoSimpatia:     fromPgInt4(v.EducacaoSimpatia),
			DesempenhoEvento:     fromPgInt4(v.DesempenhoEvento),
			DispHorario:          fromPgInt4(v.DispHorario),
			Media:                fromPgNumeric(v.Media),
			TabelaFree:           fromPgText(v.TabelaFree),
			ExtraPorEquipamento:  fromPgBool(v.ExtraPorEquipamento),
			Equipamentos:         fromPgText(v.Equipamentos),
			Email:                email,
			AvatarURL:            fromPgText(v.AvatarUrl),
		}
	case generated.GetProfissionalByIDRow:
		return ProfissionalResponse{
			ID:                   uuid.UUID(v.ID.Bytes).String(),
			UsuarioID:            uuid.UUID(v.UsuarioID.Bytes).String(),
			Nome:                 v.Nome,
			FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(),
			FuncaoProfissional:   "", // v.FuncaoNome removed
			Functions:            toJSONRaw(v.Functions),
			Endereco:             fromPgText(v.Endereco),
			Cidade:               fromPgText(v.Cidade),
			Uf:                   fromPgText(v.Uf),
			Cep:                  fromPgText(v.Cep),
			Numero:               fromPgText(v.Numero),
			Complemento:          fromPgText(v.Complemento),
			Bairro:               fromPgText(v.Bairro),
			Whatsapp:             fromPgText(v.Whatsapp),
			CpfCnpjTitular:       fromPgText(v.CpfCnpjTitular),
			Banco:                fromPgText(v.Banco),
			Agencia:              fromPgText(v.Agencia),
			Conta:                fromPgText(v.Conta),
			ContaPix:             fromPgText(v.ContaPix),
			CarroDisponivel:      fromPgBool(v.CarroDisponivel),
			TemEstudio:           fromPgBool(v.TemEstudio),
			QtdEstudio:           fromPgInt4(v.QtdEstudio),
			TipoCartao:           fromPgText(v.TipoCartao),
			Observacao:           fromPgText(v.Observacao),
			QualTec:              fromPgInt4(v.QualTec),
			EducacaoSimpatia:     fromPgInt4(v.EducacaoSimpatia),
			DesempenhoEvento:     fromPgInt4(v.DesempenhoEvento),
			DispHorario:          fromPgInt4(v.DispHorario),
			Media:                fromPgNumeric(v.Media),
			TabelaFree:           fromPgText(v.TabelaFree),
			ExtraPorEquipamento:  fromPgBool(v.ExtraPorEquipamento),
			Equipamentos:         fromPgText(v.Equipamentos),
			Email:                fromPgText(v.Email),
			AvatarURL:            fromPgText(v.AvatarUrl),
		}
	case generated.GetProfissionalByUsuarioIDRow:
		email := fromPgText(v.Email)
		if email == nil {
			email = fromPgText(v.UsuarioEmail)
		}
		return ProfissionalResponse{
			ID:                   uuid.UUID(v.ID.Bytes).String(),
			UsuarioID:            uuid.UUID(v.UsuarioID.Bytes).String(),
			Nome:                 v.Nome,
			FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(),
			FuncaoProfissional:   "",
			Functions:            toJSONRaw(v.Functions),
			Endereco:             fromPgText(v.Endereco),
			Cidade:               fromPgText(v.Cidade),
			Uf:                   fromPgText(v.Uf),
			Cep:                  fromPgText(v.Cep),
			Numero:               fromPgText(v.Numero),
			Complemento:          fromPgText(v.Complemento),
			Bairro:               fromPgText(v.Bairro),
			Whatsapp:             fromPgText(v.Whatsapp),
			CpfCnpjTitular:       fromPgText(v.CpfCnpjTitular),
			Banco:                fromPgText(v.Banco),
			Agencia:              fromPgText(v.Agencia),
			Conta:                fromPgText(v.Conta),
			ContaPix:             fromPgText(v.ContaPix),
			CarroDisponivel:      fromPgBool(v.CarroDisponivel),
			TemEstudio:           fromPgBool(v.TemEstudio),
			QtdEstudio:           fromPgInt4(v.QtdEstudio),
			TipoCartao:           fromPgText(v.TipoCartao),
			Observacao:           fromPgText(v.Observacao),
			QualTec:              fromPgInt4(v.QualTec),
			EducacaoSimpatia:     fromPgInt4(v.EducacaoSimpatia),
			DesempenhoEvento:     fromPgInt4(v.DesempenhoEvento),
			DispHorario:          fromPgInt4(v.DispHorario),
			Media:                fromPgNumeric(v.Media),
			TabelaFree:           fromPgText(v.TabelaFree),
			ExtraPorEquipamento:  fromPgBool(v.ExtraPorEquipamento),
			Equipamentos:         fromPgText(v.Equipamentos),
			Email:                email,
			AvatarURL:            fromPgText(v.AvatarUrl),
		}
	default:
		return ProfissionalResponse{}
	}
}

// Helpers for conversion
func fromPgText(t pgtype.Text) *string {
	if !t.Valid {
		return nil
	}
	return &t.String
}

func fromPgBool(b pgtype.Bool) *bool {
	if !b.Valid {
		return nil
	}
	return &b.Bool
}

func fromPgInt4(i pgtype.Int4) *int {
	if !i.Valid {
		return nil
	}
	val := int(i.Int32)
	return &val
}

func fromPgNumeric(n pgtype.Numeric) *float64 {
	if !n.Valid {
		return nil
	}
	f, _ := n.Float64Value()
	val := f.Float64
	return &val
}

func toJSONRaw(v interface{}) json.RawMessage {
	if v == nil {
		return json.RawMessage("[]")
	}
	switch val := v.(type) {
	case []byte:
		if len(val) == 0 {
			return json.RawMessage("[]")
		}
		return json.RawMessage(val)
	case string:
		if val == "" {
			return json.RawMessage("[]")
		}
		return json.RawMessage([]byte(val))
	default:
		// Fallback: marshal strictly? or return empty array
		b, _ := json.Marshal(v)
		return json.RawMessage(b)
	}
}

// Create godoc
// @Summary      Create a new profissional
// @Description  Create a new profissional record
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        request body CreateProfissionalInput true "Create Profissional Request"
// @Success      201  {object}  ProfissionalResponse
// @Failure      400  {object}  map[string]string
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais [post]
func (h *Handler) Create(c *gin.Context) {
	var input CreateProfissionalInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	userID, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"})
		return
	}

	userIDStr, ok := userID.(string)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user id type in context"})
		return
	}

	// Security: Only allow TargetUserID if user is ADMIN or OWNER
	// Also handle Region override
	userRole, _ := c.Get("role")
	roleStr, _ := userRole.(string)

	if input.TargetUserID != nil && *input.TargetUserID != "" {
		if roleStr != "SUPERADMIN" && roleStr != "BUSINESS_OWNER" {
			input.TargetUserID = nil
		}
	}

	regiao := c.GetString("regiao")
	// If input has regiao and user is admin, use it
	if input.Regiao != nil && *input.Regiao != "" {
		if roleStr == "SUPERADMIN" || roleStr == "BUSINESS_OWNER" {
			regiao = *input.Regiao
		}
	}

	prof, err := h.service.Create(c.Request.Context(), userIDStr, input, regiao)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, toResponse(*prof))
}

// List godoc
// @Summary      List profissionais
// @Description  List all profissionais
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Success      200  {array}   ProfissionalResponse
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais [get]
func (h *Handler) List(c *gin.Context) {
	regiao := c.GetString("regiao")
	profs, err := h.service.List(c.Request.Context(), regiao)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	var response []ProfissionalResponse
	for _, p := range profs {
		response = append(response, toResponse(p))
	}

	c.JSON(http.StatusOK, response)
}

// Me godoc
// @Summary      Get current authenticated profissional
// @Description  Get the profissional profile associated with the current user
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Success      200  {object}  ProfissionalResponse
// @Failure      401  {object}  map[string]string
// @Failure      404  {object}  map[string]string
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais/me [get]
func (h *Handler) Me(c *gin.Context) {
	userID, exists := c.Get("userID")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"})
		return
	}

	userIDStr, ok := userID.(string)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user id type"})
		return
	}

	regiao := c.GetString("regiao")
	prof, err := h.service.GetByUserID(c.Request.Context(), userIDStr, regiao)
	if err != nil {
		// Checks if error is "no rows in result set" -> 404
		if err.Error() == "no rows in result set" {
			c.JSON(http.StatusNotFound, gin.H{"error": "professional profile not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Reuse toResponse which handles different types via interface{} check
	// We need to add case for GetProfissionalByUsuarioIDRow in toResponse function
	c.JSON(http.StatusOK, toResponse(*prof))
}

// Get godoc
// @Summary      Get profissional by ID
// @Description  Get a profissional by ID
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id   path      string  true  "Profissional ID"
// @Success      200  {object}  ProfissionalResponse
// @Failure      400  {object}  map[string]string
// @Failure      404  {object}  map[string]string
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais/{id} [get]
func (h *Handler) Get(c *gin.Context) {
	id := c.Param("id")
	if id == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
		return
	}

	regiao := c.GetString("regiao")
	prof, err := h.service.GetByID(c.Request.Context(), id, regiao)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, toResponse(*prof))
}

// Update godoc
// @Summary      Update profissional
// @Description  Update a profissional by ID
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id   path      string  true  "Profissional ID"
// @Param        request body UpdateProfissionalInput true "Update Profissional Request"
// @Success      200  {object}  ProfissionalResponse
// @Failure      400  {object}  map[string]string
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais/{id} [put]
func (h *Handler) Update(c *gin.Context) {
	id := c.Param("id")
	if id == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
		return
	}

	var input UpdateProfissionalInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	regiao := c.GetString("regiao")
	prof, err := h.service.Update(c.Request.Context(), id, input, regiao)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, toResponse(*prof))
}

// Delete godoc
// @Summary      Delete profissional
// @Description  Delete a profissional by ID
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id   path      string  true  "Profissional ID"
// @Success      200  {object}  map[string]string
// @Failure      400  {object}  map[string]string
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais/{id} [delete]
func (h *Handler) Delete(c *gin.Context) {
	id := c.Param("id")
	if id == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
		return
	}

	regiao := c.GetString("regiao")
	err := h.service.Delete(c.Request.Context(), id, regiao)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "deleted"})
}

// Import godoc
// @Summary      Import professionals from properties
// @Description  Import professionals (Upsert by CPF)
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        request body []CreateProfissionalInput true "List of Professionals"
// @Success      200  {object}  ImportStats
// @Failure      400  {object}  map[string]string
// @Failure      500  {object}  map[string]string
// @Router       /api/profissionais/import [post]
func (h *Handler) Import(c *gin.Context) {
	var input []CreateProfissionalInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Role check: Only ADMIN or OWNER
	userRole, exists := c.Get("role")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
		return
	}
	roleStr := userRole.(string)
	if roleStr != "SUPERADMIN" && roleStr != "BUSINESS_OWNER" {
		c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
		return
	}

	regiao := c.GetString("regiao")
	stats, errs := h.service.Import(c.Request.Context(), input, regiao)

	// Construct response with errors if any
	response := gin.H{
		"created":      stats.Created,
		"updated":      stats.Updated,
		"errors_count": stats.Errors,
	}

	if len(errs) > 0 {
		var errMsgs []string
		for _, e := range errs {
			errMsgs = append(errMsgs, e.Error())
		}
		response["errors"] = errMsgs
	}

	c.JSON(http.StatusOK, response)
}

// CheckCPF godoc
// @Summary      Check if professional exists by CPF
// @Description  Check existence and claim status
// @Tags         profissionais
// @Accept       json
// @Produce      json
// @Param        cpf  query     string  true  "CPF/CNPJ"
// @Success      200  {object}  map[string]interface{}
// @Failure      400  {object}  map[string]string
// @Router       /api/public/profissionais/check [get]
func (h *Handler) CheckCPF(c *gin.Context) {
	cpf := c.Query("cpf")
	if cpf == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "cpf required"})
		return
	}

	// Clean CPF (optional, but good practice)
	// For now rely on exact match or client cleaning.
	// Ideally service should clean.

	// Check existence
	regiao := c.GetString("regiao")
	exists, claimed, name, err := h.service.CheckExistence(c.Request.Context(), cpf, regiao)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Return limited info for public check
	c.JSON(http.StatusOK, gin.H{
		"exists":  exists,
		"claimed": claimed,
		"name":    name, // Returning name might be PII, but usually acceptable for confirmation "Is this you?"
	})
}
