package mysql

import (
	"context"
	"fmt"

	"git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
	"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
	richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
	"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)

type DataBase struct {
	conn *mysql.DB
}

func NewPatientRepo(db *mysql.DB) *DataBase {

	return &DataBase{
		conn: db,
	}
}

func (db *DataBase) GetPatients(ctx context.Context, f analytic.PatientFilter) ([]entity.UserMeta, error) {
	const Op = "repository.mysql.patient.get"

	tx, err := db.conn.Conn().BeginTx(ctx, nil)
	if err != nil {
		return []entity.UserMeta{}, richerror.New(Op).WithErr(err)
	}
	defer tx.Rollback()

	query := `
		SELECT
			um.id,
			um.user_id,
			u.birthDate,
			u.sex,
			um.birthPlaceState,
			um.birthPlaceCity,
			um.nationality,
			um.addressState,
			um.addressCity,
			um.address,
			um.phone,
			um.mobile,
			um.spouseName,
			um.created_at,
			um.updated_at
		FROM user_metas um
		JOIN users u ON u.id = um.user_id
		WHERE 1=1
	`

	args := []any{}

	// Birthdate filters (FROM = born after)
	if f.DOBFrom != nil && *f.DOBFrom != "" {
		query += " AND u.birth_date >= ?"
		args = append(args, *f.DOBFrom)
	}
	if f.DOBTo != nil && *f.DOBTo != "" {
		query += " AND u.birth_date <= ?"
		args = append(args, *f.DOBTo)
	}

	// Sex
	if f.Sex != nil && *f.Sex != "" {
		query += " AND u.sex = ?"
		args = append(args, *f.Sex)
	}

	// Nationality
	if f.Nationality != "" {
		query += " AND um.nationality = ?"
		args = append(args, f.Nationality)
	}

	// Address
	if f.AddressState != 0 {
		query += " AND um.addressState = ?"
		args = append(args, f.AddressState)
	}
	if f.AddressCity != 0 {
		query += " AND um.addressCity = ?"
		args = append(args, f.AddressCity)
	}

	// Search on fields from user_metas and users
	if f.Search != nil && *f.Search != "" {
		like := "%" + *f.Search + "%"
		query += `
			AND (
				um.fName LIKE ? OR
				um.mName LIKE ? OR
				um.spouseName LIKE ? OR
				um.phone LIKE ? OR
				um.mobile LIKE ?
			)
		`
		args = append(args, like, like, like, like, like)
	}

	query += " ORDER BY id DESC"

	if f.Limit > 0 {
		query += " LIMIT ?"
		args = append(args, f.Limit)
	}
	if f.Offset > 0 {
		query += " OFFSET ?"
		args = append(args, f.Offset)
	}

	rows, err := tx.QueryContext(ctx, query, args...)
	if err != nil {

		return nil, richerror.New(Op).WithErr(err)
	}
	defer rows.Close()

	var result []entity.UserMeta

	for rows.Next() {
		var u entity.UserMeta
		if err := rows.Scan(
			&u.ID, &u.UserID, &u.BirthDate, &u.Gender, &u.BirthPlaceState, &u.BirthPlaceCity,
			&u.Religion, &u.Nationality, &u.AddressState, &u.AddressCity,
			&u.Address, &u.Mobile, &u.SpouseName, &u.SpouseAlive,
			&u.CreatedAt, &u.UpdatedAt,
		); err != nil {
			return nil, richerror.New(Op).WithErr(err)
		}
		result = append(result, u)
	}

	if err := rows.Err(); err != nil {
		return nil, richerror.New(Op).WithErr(err)
	}

	return result, nil

}

func (db *DataBase) CountPatients(ctx context.Context, f analytic.PatientFilter) (int, error) {
	const Op = "repository.mysql.patient.count"

	tx, err := db.conn.Conn().BeginTx(ctx, nil)
	if err != nil {
		return 0, richerror.New(Op).WithErr(err)
	}
	defer tx.Rollback()

	query := `
		SELECT COUNT(*)
		FROM user_metas um
		JOIN users u ON u.id = um.user_id
		WHERE 1=1
	`

	args := []any{}

	// Birthdate range
	if f.DOBFrom != nil && *f.DOBFrom != "" {
		query += " AND u.birth_date >= ?"
		args = append(args, *f.DOBFrom)
	}
	if f.DOBTo != nil && *f.DOBTo != "" {
		query += " AND u.birth_date <= ?"
		args = append(args, *f.DOBTo)
	}

	// Sex
	if f.Sex != nil && *f.Sex != "" {
		query += " AND u.sex = ?"
		args = append(args, *f.Sex)
	}

	// Nationality
	if f.Nationality != "" {
		query += " AND um.nationality = ?"
		args = append(args, f.Nationality)
	}

	// Address
	if f.AddressState != 0 {
		query += " AND um.addressState = ?"
		args = append(args, f.AddressState)
	}
	if f.AddressCity != 0 {
		query += " AND um.addressCity = ?"
		args = append(args, f.AddressCity)
	}

	// Search
	if f.Search != nil && *f.Search != "" {
		like := "%" + *f.Search + "%"
		query += `
			AND (
				um.fName LIKE ? OR
				um.mName LIKE ? OR
				um.spouseName LIKE ? OR
				um.phone LIKE ? OR
				um.mobile LIKE ?
			)
		`
		args = append(args, like, like, like, like, like)
	}

	var count int

	err = tx.QueryRowContext(ctx, query, args...).Scan(&count)
	if err != nil {
		return 0, fmt.Errorf("%s: query error: %w", Op, err)
	}

	return count, nil
}

func (db *DataBase) SummaryByCity(ctx context.Context, provinceID uint, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
	const Op = "repository.mysql.patient.map_summary"

	tx, err := db.conn.Conn().BeginTx(ctx, nil)
	if err != nil {
		return nil, richerror.New(Op).WithErr(err)
	}
	defer tx.Rollback()

	query := `
        SELECT
            um.addressCity AS city_id,
            ANY_VALUE(um.lat) AS lat,
            ANY_VALUE(um.lng) AS lng,
            COUNT(*) AS user_count
        FROM user_metas um
        JOIN users u ON u.id = um.user_id
        JOIN cities c ON c.id = um.addressCity
        WHERE c.state_id = ?
    `

	args := []any{provinceID}
	// Birthdate filters
	if f.MinDOB != nil && *f.MinDOB != "" {
		query += " AND u.birth_date >= ?"
		args = append(args, *f.MinDOB)
	}
	if f.MaxDOB != nil && *f.MaxDOB != "" {
		query += " AND u.birth_date <= ?"
		args = append(args, *f.MaxDOB)
	}

	// Sex filter
	if f.Sex != nil && *f.Sex != "" {
		query += " AND u.sex = ?"
		args = append(args, *f.Sex)
	}

	// Search filter
	if f.Search != nil && *f.Search != "" {
		like := "%" + *f.Search + "%"
		query += `
            AND (
                um.fName LIKE ? OR
                um.mName LIKE ? OR
                um.spouseName LIKE ? OR
                um.phone LIKE ? OR
                um.mobile LIKE ?
            )
        `
		args = append(args, like, like, like, like, like)
	}

	// Group by city
	query += " GROUP BY um.addressCity"

	rows, err := tx.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("%s: query error: %w", Op, err)
	}
	defer rows.Close()

	result := make(map[uint][]entity.MapSummaryItem)

	for rows.Next() {
		var item entity.MapSummaryItem

		err := rows.Scan(
			&item.ID,
			&item.Latitude,
			&item.Longitude,
			&item.Count,
		)
		if err != nil {
			return nil, fmt.Errorf("%s: scan error: %w", Op, err)
		}

		result[item.ID] = append(result[item.ID], item)
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("%s: rows error: %w", Op, err)
	}

	return result, nil
}

func (db *DataBase) SummaryByProvince(ctx context.Context, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {

	const Op = "repository.mysql.patient.summary_by_province"

	tx, err := db.conn.Conn().BeginTx(ctx, nil)
	if err != nil {
		return nil, richerror.New(Op).WithErr(err)
	}
	defer tx.Rollback()

	query := `
        SELECT
            c.state_id,
            ANY_VALUE(c.lat) AS lat,
            ANY_VALUE(c.lng) AS lng,
            COUNT(*) AS total
        FROM user_metas um
        JOIN users u ON u.id = um.user_id
        JOIN cities c ON c.id = um.addressCity
        WHERE 1 = 1
    `
	args := []any{}

	// Birthdate filters
	if f.MinDOB != nil && *f.MinDOB != "" {
		query += " AND u.birth_date >= ?"
		args = append(args, *f.MinDOB)
	}
	if f.MaxDOB != nil && *f.MaxDOB != "" {
		query += " AND u.birth_date <= ?"
		args = append(args, *f.MaxDOB)
	}

	// Sex filter
	if f.Sex != nil && *f.Sex != "" {
		query += " AND u.sex = ?"
		args = append(args, *f.Sex)
	}

	// Search filter
	if f.Search != nil && *f.Search != "" {
		like := "%" + *f.Search + "%"
		query += `
            AND (
                um.fName LIKE ? OR
                um.mName LIKE ? OR
                um.spouseName LIKE ? OR
                um.phone LIKE ? OR
                um.mobile LIKE ?
            )
        `
		args = append(args, like, like, like, like, like)
	}

	query += " GROUP BY c.state_id"

	rows, err := tx.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("%s: query error: %w", Op, err)
	}
	defer rows.Close()

	result := make(map[uint][]entity.MapSummaryItem)

	for rows.Next() {
		var item entity.MapSummaryItem

		if err := rows.Scan(&item.ID, &item.Latitude, &item.Longitude, &item.Count); err != nil {
			return nil, fmt.Errorf("%s: scan error: %w", Op, err)
		}

		result[item.ID] = []entity.MapSummaryItem{item}
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("%s: rows error: %w", Op, err)
	}

	return result, nil
}
