package secciones

import (
	"database/sql"
	"strings"
)

// Seccion representa una sección (categoría) de un supermercado.
type Seccion struct {
	Id          int    `json:"id"`
	Nombre      string `json:"nombre"`
	ColorInicio string `json:"color_inicio"`
	ColorFin    string `json:"color_fin"`
}

const (
	ColorInicioPorDefecto = "#198754"
	ColorFinPorDefecto    = "#0d6efd"
)

type DataWrapper struct {
	Secciones []Seccion `json:"secciones"`
}

// CrearTabla crea la tabla de secciones si todavía no existe, elimina la
// columna comercio de las bases de datos antiguas y completa las secciones
// que faltan a partir de las categorías de los productos.
func CrearTabla(db *sql.DB) error {

	sentencia := `CREATE TABLE IF NOT EXISTS secciones (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		nombre TEXT NOT NULL COLLATE NOCASE UNIQUE,
		color_inicio TEXT NOT NULL DEFAULT '#198754',
		color_fin TEXT NOT NULL DEFAULT '#0d6efd'
	);`

	if _, err := db.Exec(sentencia); err != nil {

		return err

	}

	if err := asegurarColumnasColor(db); err != nil {

		return err

	}

	if err := eliminarComercio(db); err != nil {

		return err

	}

	return rellenarDesdeProductos(db)

}

func columnasTabla(db *sql.DB) (map[string]bool, error) {

	columnas := map[string]bool{}

	filas, err := db.Query(`PRAGMA table_info(secciones);`)
	if err != nil {

		return nil, err

	}
	defer filas.Close()

	for filas.Next() {

		var (
			id              int
			nombre          string
			tipo            string
			noNulo          int
			valorPorDefecto sql.NullString
			clavePrimaria   int
		)

		if err := filas.Scan(&id, &nombre, &tipo, &noNulo, &valorPorDefecto, &clavePrimaria); err != nil {

			return nil, err

		}

		columnas[nombre] = true

	}

	if err := filas.Err(); err != nil {

		return nil, err

	}

	return columnas, nil

}

func asegurarColumnasColor(db *sql.DB) error {

	columnas, err := columnasTabla(db)
	if err != nil {

		return err

	}

	if !columnas["color_inicio"] {

		if _, err := db.Exec(`ALTER TABLE secciones ADD COLUMN color_inicio TEXT NOT NULL DEFAULT '#198754';`); err != nil {

			return err

		}

	}

	if !columnas["color_fin"] {

		if _, err := db.Exec(`ALTER TABLE secciones ADD COLUMN color_fin TEXT NOT NULL DEFAULT '#0d6efd';`); err != nil {

			return err

		}

	}

	return nil

}

// eliminarComercio reconstruye la tabla para quitar la antigua columna
// comercio y dejar un único registro por nombre de sección.
func eliminarComercio(db *sql.DB) error {

	columnas, err := columnasTabla(db)
	if err != nil {

		return err

	}

	if !columnas["comercio"] {

		return nil

	}

	transaccion, err := db.Begin()
	if err != nil {

		return err

	}
	defer transaccion.Rollback()

	if _, err := transaccion.Exec(`DROP TABLE IF EXISTS secciones_sin_comercio;`); err != nil {

		return err

	}

	if _, err := transaccion.Exec(`CREATE TABLE secciones_sin_comercio (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		nombre TEXT NOT NULL COLLATE NOCASE UNIQUE,
		color_inicio TEXT NOT NULL DEFAULT '#198754',
		color_fin TEXT NOT NULL DEFAULT '#0d6efd'
	);`); err != nil {

		return err

	}

	if _, err := transaccion.Exec(`
		INSERT INTO secciones_sin_comercio (id, nombre, color_inicio, color_fin)
		SELECT s.id, TRIM(s.nombre), s.color_inicio, s.color_fin
		FROM secciones s
		WHERE s.id IN (
			SELECT MIN(id)
			FROM secciones
			WHERE TRIM(nombre) <> ''
			GROUP BY UPPER(TRIM(nombre))
		)
		ORDER BY s.id;
	`); err != nil {

		return err

	}

	if _, err := transaccion.Exec(`DROP TABLE secciones;`); err != nil {

		return err

	}

	if _, err := transaccion.Exec(`ALTER TABLE secciones_sin_comercio RENAME TO secciones;`); err != nil {

		return err

	}

	return transaccion.Commit()

}

// rellenarDesdeProductos añade las categorías de los productos que todavía
// no tengan una sección, sin repetir nombres.
func rellenarDesdeProductos(db *sql.DB) error {

	var existe int

	err := db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = 'productos';`).Scan(&existe)
	if err != nil {

		return err

	}

	if existe == 0 {

		return nil

	}

	_, err = db.Exec(`
		INSERT OR IGNORE INTO secciones (nombre, color_inicio, color_fin)
		SELECT UPPER(TRIM(categoria)), '#198754', '#0d6efd'
		FROM productos
		WHERE categoria IS NOT NULL AND TRIM(categoria) <> ''
		GROUP BY UPPER(TRIM(categoria))
		ORDER BY UPPER(TRIM(categoria));
	`)

	return err

}

func normalizarColor(color string, colorPorDefecto string) string {

	color = strings.TrimSpace(color)

	if len(color) != 7 || color[0] != '#' {

		return colorPorDefecto

	}

	for _, caracter := range color[1:] {

		if !((caracter >= '0' && caracter <= '9') ||
			(caracter >= 'a' && caracter <= 'f') ||
			(caracter >= 'A' && caracter <= 'F')) {

			return colorPorDefecto

		}

	}

	return strings.ToLower(color)

}

// InsertarSeccion añade una nueva sección y devuelve su identificador.
func InsertarSeccion(db *sql.DB, s *Seccion) (int64, error) {

	s.Nombre = strings.TrimSpace(s.Nombre)
	s.ColorInicio = normalizarColor(s.ColorInicio, ColorInicioPorDefecto)
	s.ColorFin = normalizarColor(s.ColorFin, ColorFinPorDefecto)

	sentencia := `INSERT INTO secciones (nombre, color_inicio, color_fin) VALUES (?, ?, ?);`

	resultado, err := db.Exec(sentencia, s.Nombre, s.ColorInicio, s.ColorFin)
	if err != nil {

		return 0, err

	}

	return resultado.LastInsertId()
}

// ObtenerSecciones devuelve todas las secciones ordenadas por nombre.
func ObtenerSecciones(db *sql.DB) ([]Seccion, error) {

	filas, err := db.Query(`SELECT id, nombre, color_inicio, color_fin FROM secciones ORDER BY nombre;`)
	if err != nil {

		return nil, err

	}
	defer filas.Close()

	secciones := []Seccion{}

	for filas.Next() {

		var s Seccion
		if err := filas.Scan(&s.Id, &s.Nombre, &s.ColorInicio, &s.ColorFin); err != nil {

			return nil, err

		}

		secciones = append(secciones, s)

	}

	return secciones, filas.Err()
}

// ObtenerSeccion devuelve una sección por su identificador.
// Si no existe, devuelve nil y error nulo.
func ObtenerSeccion(db *sql.DB, id int) (*Seccion, error) {

	var s Seccion

	err := db.QueryRow(`SELECT id, nombre, color_inicio, color_fin FROM secciones WHERE id = ?;`, id).
		Scan(&s.Id, &s.Nombre, &s.ColorInicio, &s.ColorFin)

	if err == sql.ErrNoRows {

		return nil, nil

	}

	if err != nil {

		return nil, err

	}

	return &s, nil
}

// ActualizarSeccion modifica el nombre y los colores del degradado
// de una sección existente.
func ActualizarSeccion(db *sql.DB, id int, nombre string, colorInicio string, colorFin string) (int64, error) {

	nombre = strings.TrimSpace(nombre)
	colorInicio = normalizarColor(colorInicio, ColorInicioPorDefecto)
	colorFin = normalizarColor(colorFin, ColorFinPorDefecto)

	sentencia := `UPDATE secciones SET nombre = ?, color_inicio = ?, color_fin = ? WHERE id = ?;`

	resultado, err := db.Exec(sentencia, nombre, colorInicio, colorFin, id)
	if err != nil {

		return 0, err

	}

	return resultado.RowsAffected()
}

// EliminarSeccion borra una sección por su identificador.
func EliminarSeccion(db *sql.DB, id int) (int64, error) {

	resultado, err := db.Exec(`DELETE FROM secciones WHERE id = ?;`, id)
	if err != nil {

		return 0, err

	}

	return resultado.RowsAffected()
}
