package zone

import (
	"database/sql"
	"fmt"
	"log"
	"sync"
)

// ZoneDatabase handles all database operations for zones
type ZoneDatabase struct {
	db      *sql.DB
	queries map[string]*sql.Stmt
	mutex   sync.RWMutex
}

// NewZoneDatabase creates a new zone database instance
func NewZoneDatabase(db *sql.DB) *ZoneDatabase {
	zdb := &ZoneDatabase{
		db:      db,
		queries: make(map[string]*sql.Stmt),
	}

	if err := zdb.prepareStatements(); err != nil {
		log.Printf("%s Failed to prepare database statements: %v", LogPrefixZone, err)
	}

	return zdb
}

// LoadZoneData loads all zone configuration and spawn data
func (zdb *ZoneDatabase) LoadZoneData(zoneID int32) (*ZoneData, error) {
	zoneData := &ZoneData{
		ZoneID: zoneID,
	}

	// Load zone configuration
	if err := zdb.loadZoneConfiguration(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load zone configuration: %v", err)
	}

	// Load spawn locations
	if err := zdb.loadSpawnLocations(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load spawn locations: %v", err)
	}

	// Load spawn entries
	if err := zdb.loadSpawnEntries(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load spawn entries: %v", err)
	}

	// Load NPCs
	if err := zdb.loadNPCs(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load NPCs: %v", err)
	}

	// Load objects
	if err := zdb.loadObjects(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load objects: %v", err)
	}

	// Load widgets
	if err := zdb.loadWidgets(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load widgets: %v", err)
	}

	// Load signs
	if err := zdb.loadSigns(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load signs: %v", err)
	}

	// Load ground spawns
	if err := zdb.loadGroundSpawns(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load ground spawns: %v", err)
	}

	// Load transporters
	if err := zdb.loadTransporters(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load transporters: %v", err)
	}

	// Load location grids
	if err := zdb.loadLocationGrids(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load location grids: %v", err)
	}

	// Load revive points
	if err := zdb.loadRevivePoints(zoneData); err != nil {
		return nil, fmt.Errorf("failed to load revive points: %v", err)
	}

	log.Printf("%s Loaded zone data for zone %d", LogPrefixZone, zoneID)
	return zoneData, nil
}

// SaveZoneConfiguration saves zone configuration to database
func (zdb *ZoneDatabase) SaveZoneConfiguration(config *ZoneConfiguration) error {
	zdb.mutex.Lock()
	defer zdb.mutex.Unlock()

	stmt := zdb.queries["updateZoneConfig"]
	if stmt == nil {
		return fmt.Errorf("update zone config statement not prepared")
	}

	_, err := stmt.Exec(
		config.Name,
		config.File,
		config.Description,
		config.SafeX,
		config.SafeY,
		config.SafeZ,
		config.SafeHeading,
		config.Underworld,
		config.MinLevel,
		config.MaxLevel,
		config.MinStatus,
		config.MinVersion,
		config.InstanceType,
		config.MaxPlayers,
		config.DefaultLockoutTime,
		config.DefaultReenterTime,
		config.DefaultResetTime,
		config.GroupZoneOption,
		config.ExpansionFlag,
		config.HolidayFlag,
		config.CanBind,
		config.CanGate,
		config.CanEvac,
		config.CityZone,
		config.AlwaysLoaded,
		config.WeatherAllowed,
		config.ZoneID,
	)

	if err != nil {
		return fmt.Errorf("failed to save zone configuration: %v", err)
	}

	return nil
}

// LoadSpawnLocation loads a specific spawn location
func (zdb *ZoneDatabase) LoadSpawnLocation(locationID int32) (*SpawnLocation, error) {
	zdb.mutex.RLock()
	defer zdb.mutex.RUnlock()

	stmt := zdb.queries["selectSpawnLocation"]
	if stmt == nil {
		return nil, fmt.Errorf("select spawn location statement not prepared")
	}

	location := &SpawnLocation{}
	err := stmt.QueryRow(locationID).Scan(
		&location.ID,
		&location.X,
		&location.Y,
		&location.Z,
		&location.Heading,
		&location.Pitch,
		&location.Roll,
		&location.SpawnType,
		&location.RespawnTime,
		&location.ExpireTime,
		&location.ExpireOffset,
		&location.Conditions,
		&location.ConditionalValue,
		&location.SpawnPercentage,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to load spawn location %d: %v", locationID, err)
	}

	return location, nil
}

// SaveSpawnLocation saves a spawn location to database
func (zdb *ZoneDatabase) SaveSpawnLocation(location *SpawnLocation) error {
	zdb.mutex.Lock()
	defer zdb.mutex.Unlock()

	var stmt *sql.Stmt
	var err error

	if location.ID == 0 {
		// Insert new location
		stmt = zdb.queries["insertSpawnLocation"]
		if stmt == nil {
			return fmt.Errorf("insert spawn location statement not prepared")
		}

		err = stmt.QueryRow(
			location.X,
			location.Y,
			location.Z,
			location.Heading,
			location.Pitch,
			location.Roll,
			location.SpawnType,
			location.RespawnTime,
			location.ExpireTime,
			location.ExpireOffset,
			location.Conditions,
			location.ConditionalValue,
			location.SpawnPercentage,
		).Scan(&location.ID)
	} else {
		// Update existing location
		stmt = zdb.queries["updateSpawnLocation"]
		if stmt == nil {
			return fmt.Errorf("update spawn location statement not prepared")
		}

		_, err = stmt.Exec(
			location.X,
			location.Y,
			location.Z,
			location.Heading,
			location.Pitch,
			location.Roll,
			location.SpawnType,
			location.RespawnTime,
			location.ExpireTime,
			location.ExpireOffset,
			location.Conditions,
			location.ConditionalValue,
			location.SpawnPercentage,
			location.ID,
		)
	}

	if err != nil {
		return fmt.Errorf("failed to save spawn location: %v", err)
	}

	return nil
}

// DeleteSpawnLocation deletes a spawn location from database
func (zdb *ZoneDatabase) DeleteSpawnLocation(locationID int32) error {
	zdb.mutex.Lock()
	defer zdb.mutex.Unlock()

	stmt := zdb.queries["deleteSpawnLocation"]
	if stmt == nil {
		return fmt.Errorf("delete spawn location statement not prepared")
	}

	_, err := stmt.Exec(locationID)
	if err != nil {
		return fmt.Errorf("failed to delete spawn location %d: %v", locationID, err)
	}

	return nil
}

// LoadSpawnGroups loads spawn group associations for a zone
func (zdb *ZoneDatabase) LoadSpawnGroups(zoneID int32) (map[int32][]int32, error) {
	zdb.mutex.RLock()
	defer zdb.mutex.RUnlock()

	stmt := zdb.queries["selectSpawnGroups"]
	if stmt == nil {
		return nil, fmt.Errorf("select spawn groups statement not prepared")
	}

	rows, err := stmt.Query(zoneID)
	if err != nil {
		return nil, fmt.Errorf("failed to query spawn groups: %v", err)
	}
	defer rows.Close()

	groups := make(map[int32][]int32)

	for rows.Next() {
		var groupID, locationID int32
		if err := rows.Scan(&groupID, &locationID); err != nil {
			return nil, fmt.Errorf("failed to scan spawn group row: %v", err)
		}

		groups[groupID] = append(groups[groupID], locationID)
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating spawn groups: %v", err)
	}

	return groups, nil
}

// SaveSpawnGroup saves spawn group associations
func (zdb *ZoneDatabase) SaveSpawnGroup(groupID int32, locationIDs []int32) error {
	zdb.mutex.Lock()
	defer zdb.mutex.Unlock()

	// Start transaction
	tx, err := zdb.db.Begin()
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %v", err)
	}
	defer tx.Rollback()

	// Delete existing associations
	deleteStmt := zdb.queries["deleteSpawnGroup"]
	if deleteStmt == nil {
		return fmt.Errorf("delete spawn group statement not prepared")
	}

	_, err = tx.Stmt(deleteStmt).Exec(groupID)
	if err != nil {
		return fmt.Errorf("failed to delete existing spawn group: %v", err)
	}

	// Insert new associations
	insertStmt := zdb.queries["insertSpawnGroup"]
	if insertStmt == nil {
		return fmt.Errorf("insert spawn group statement not prepared")
	}

	for _, locationID := range locationIDs {
		_, err = tx.Stmt(insertStmt).Exec(groupID, locationID)
		if err != nil {
			return fmt.Errorf("failed to insert spawn group association: %v", err)
		}
	}

	// Commit transaction
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit spawn group transaction: %v", err)
	}

	return nil
}

// Close closes all prepared statements and database connection
func (zdb *ZoneDatabase) Close() error {
	zdb.mutex.Lock()
	defer zdb.mutex.Unlock()

	// Close all prepared statements
	for name, stmt := range zdb.queries {
		if err := stmt.Close(); err != nil {
			log.Printf("%s Error closing statement %s: %v", LogPrefixZone, name, err)
		}
	}

	zdb.queries = make(map[string]*sql.Stmt)
	return nil
}

// Private helper methods

func (zdb *ZoneDatabase) prepareStatements() error {
	statements := map[string]string{
		"updateZoneConfig": `
			UPDATE zones SET
				name = ?, file = ?, description = ?, safe_x = ?, safe_y = ?, safe_z = ?,
				safe_heading = ?, underworld = ?, min_level = ?, max_level = ?, min_status = ?,
				min_version = ?, instance_type = ?, max_players = ?, default_lockout_time = ?,
				default_reenter_time = ?, default_reset_time = ?, group_zone_option = ?,
				expansion_flag = ?, holiday_flag = ?, can_bind = ?, can_gate = ?, can_evac = ?,
				city_zone = ?, always_loaded = ?, weather_allowed = ?
			WHERE id = ?`,

		"selectZoneConfig": `
			SELECT id, name, file, description, safe_x, safe_y, safe_z, safe_heading, underworld,
				min_level, max_level, min_status, min_version, instance_type, max_players,
				default_lockout_time, default_reenter_time, default_reset_time, group_zone_option,
				expansion_flag, holiday_flag, can_bind, can_gate, can_evac, city_zone, always_loaded,
				weather_allowed
			FROM zones WHERE id = ?`,

		"selectSpawnLocations": `
			SELECT id, x, y, z, heading, pitch, roll, spawn_type, respawn_time, expire_time,
				expire_offset, conditions, conditional_value, spawn_percentage
			FROM spawn_location_placement WHERE zone_id = ?
			ORDER BY id`,

		"selectSpawnLocation": `
			SELECT id, x, y, z, heading, pitch, roll, spawn_type, respawn_time, expire_time,
				expire_offset, conditions, conditional_value, spawn_percentage
			FROM spawn_location_placement WHERE id = ?`,

		"insertSpawnLocation": `
			INSERT INTO spawn_location_placement
				(x, y, z, heading, pitch, roll, spawn_type, respawn_time, expire_time,
				 expire_offset, conditions, conditional_value, spawn_percentage)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
			RETURNING id`,

		"updateSpawnLocation": `
			UPDATE spawn_location_placement SET
				x = ?, y = ?, z = ?, heading = ?, pitch = ?, roll = ?, spawn_type = ?,
				respawn_time = ?, expire_time = ?, expire_offset = ?, conditions = ?,
				conditional_value = ?, spawn_percentage = ?
			WHERE id = ?`,

		"deleteSpawnLocation": `DELETE FROM spawn_location_placement WHERE id = ?`,

		"selectSpawnEntries": `
			SELECT id, spawn_type, spawn_entry_id, name, level, encounter_level, model, size,
				hp, power, heroic, gender, race, adventure_class, tradeskill_class, attack_type,
				min_level, max_level, encounter_type, show_name, targetable, show_level,
				command_primary, command_secondary, loot_tier, min_gold, max_gold, harvest_type,
				icon
			FROM spawn_location_entry WHERE zone_id = ?
			ORDER BY id`,

		"selectNPCs": `
			SELECT id, spawn_entry_id, name, level, encounter_level, model, size, hp, power,
				heroic, gender, race, adventure_class, tradeskill_class, attack_type, min_level,
				max_level, encounter_type, show_name, targetable, show_level, loot_tier,
				min_gold, max_gold, aggro_radius, cast_percentage, randomize
			FROM spawn_npcs WHERE zone_id = ?
			ORDER BY id`,

		"selectObjects": `
			SELECT id, spawn_entry_id, name, model, size, device_id, icon, sound_name
			FROM spawn_objects WHERE zone_id = ?
			ORDER BY id`,

		"selectWidgets": `
			SELECT id, spawn_entry_id, name, model, widget_type, open_type, open_time,
				close_time, open_sound, close_sound, open_graphic, close_graphic,
				linked_spawn_id, action_spawn_id, house_id, include_location,
				include_heading
			FROM spawn_widgets WHERE zone_id = ?
			ORDER BY id`,

		"selectSigns": `
			SELECT id, spawn_entry_id, name, model, sign_type, zone_id_destination,
				widget_id, title, description, zone_x, zone_y, zone_z, zone_heading,
				include_location, include_heading
			FROM spawn_signs WHERE zone_id = ?
			ORDER BY id`,

		"selectGroundSpawns": `
			SELECT id, spawn_entry_id, name, model, harvest_type, number_harvests,
				max_number_harvests, collection_skill, respawn_timer
			FROM spawn_ground_spawns WHERE zone_id = ?
			ORDER BY id`,

		"selectTransporters": `
			SELECT id, type, display_name, message, destination_zone_id, destination_x,
				destination_y, destination_z, destination_heading, cost, unique_id,
				min_level, max_level, quest_req, quest_step_req, quest_complete,
				map_x, map_y, expansion_flag, holiday_flag, min_client_version,
				max_client_version, flight_path_id, mount_id, mount_red_color,
				mount_green_color, mount_blue_color
			FROM transporters WHERE zone_id = ?
			ORDER BY id`,

		"selectLocationGrids": `
			SELECT id, grid_id, name, include_y, discovery
			FROM location_grids WHERE zone_id = ?
			ORDER BY id`,

		"selectLocationGridLocations": `
			SELECT location_id, x, y, z, name
			FROM location_grid_locations WHERE grid_id = ?
			ORDER BY location_id`,

		"selectRevivePoints": `
			SELECT id, zone_id, location_name, x, y, z, heading, always_included
			FROM revive_points WHERE zone_id = ?
			ORDER BY id`,

		"selectSpawnGroups": `
			SELECT group_id, location_id
			FROM spawn_location_group WHERE zone_id = ?
			ORDER BY group_id, location_id`,

		"insertSpawnGroup": `
			INSERT INTO spawn_location_group (group_id, location_id) VALUES (?, ?)`,

		"deleteSpawnGroup": `
			DELETE FROM spawn_location_group WHERE group_id = ?`,
	}

	for name, query := range statements {
		stmt, err := zdb.db.Prepare(query)
		if err != nil {
			return fmt.Errorf("failed to prepare statement %s: %v", name, err)
		}
		zdb.queries[name] = stmt
	}

	return nil
}

func (zdb *ZoneDatabase) loadZoneConfiguration(zoneData *ZoneData) error {
	stmt := zdb.queries["selectZoneConfig"]
	if stmt == nil {
		return fmt.Errorf("select zone config statement not prepared")
	}

	config := &ZoneConfiguration{}
	err := stmt.QueryRow(zoneData.ZoneID).Scan(
		&config.ZoneID,
		&config.Name,
		&config.File,
		&config.Description,
		&config.SafeX,
		&config.SafeY,
		&config.SafeZ,
		&config.SafeHeading,
		&config.Underworld,
		&config.MinLevel,
		&config.MaxLevel,
		&config.MinStatus,
		&config.MinVersion,
		&config.InstanceType,
		&config.MaxPlayers,
		&config.DefaultLockoutTime,
		&config.DefaultReenterTime,
		&config.DefaultResetTime,
		&config.GroupZoneOption,
		&config.ExpansionFlag,
		&config.HolidayFlag,
		&config.CanBind,
		&config.CanGate,
		&config.CanEvac,
		&config.CityZone,
		&config.AlwaysLoaded,
		&config.WeatherAllowed,
	)

	if err != nil {
		return fmt.Errorf("failed to load zone configuration: %v", err)
	}

	zoneData.Configuration = config
	return nil
}

func (zdb *ZoneDatabase) loadSpawnLocations(zoneData *ZoneData) error {
	stmt := zdb.queries["selectSpawnLocations"]
	if stmt == nil {
		return fmt.Errorf("select spawn locations statement not prepared")
	}

	rows, err := stmt.Query(zoneData.ZoneID)
	if err != nil {
		return fmt.Errorf("failed to query spawn locations: %v", err)
	}
	defer rows.Close()

	locations := make(map[int32]*SpawnLocation)

	for rows.Next() {
		location := &SpawnLocation{}
		err := rows.Scan(
			&location.ID,
			&location.X,
			&location.Y,
			&location.Z,
			&location.Heading,
			&location.Pitch,
			&location.Roll,
			&location.SpawnType,
			&location.RespawnTime,
			&location.ExpireTime,
			&location.ExpireOffset,
			&location.Conditions,
			&location.ConditionalValue,
			&location.SpawnPercentage,
		)

		if err != nil {
			return fmt.Errorf("failed to scan spawn location: %v", err)
		}

		locations[location.ID] = location
	}

	if err := rows.Err(); err != nil {
		return fmt.Errorf("error iterating spawn locations: %v", err)
	}

	zoneData.SpawnLocations = locations
	return nil
}

func (zdb *ZoneDatabase) loadSpawnEntries(zoneData *ZoneData) error {
	// Similar implementation for spawn entries
	zoneData.SpawnEntries = make(map[int32]*SpawnEntry)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadNPCs(zoneData *ZoneData) error {
	// Similar implementation for NPCs
	zoneData.NPCs = make(map[int32]*NPCTemplate)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadObjects(zoneData *ZoneData) error {
	// Similar implementation for objects
	zoneData.Objects = make(map[int32]*ObjectTemplate)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadWidgets(zoneData *ZoneData) error {
	// Similar implementation for widgets
	zoneData.Widgets = make(map[int32]*WidgetTemplate)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadSigns(zoneData *ZoneData) error {
	// Similar implementation for signs
	zoneData.Signs = make(map[int32]*SignTemplate)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadGroundSpawns(zoneData *ZoneData) error {
	// Similar implementation for ground spawns
	zoneData.GroundSpawns = make(map[int32]*GroundSpawnTemplate)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadTransporters(zoneData *ZoneData) error {
	// Similar implementation for transporters
	zoneData.Transporters = make(map[int32]*TransportDestination)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadLocationGrids(zoneData *ZoneData) error {
	// Similar implementation for location grids
	zoneData.LocationGrids = make(map[int32]*LocationGrid)
	return nil // TODO: Implement
}

func (zdb *ZoneDatabase) loadRevivePoints(zoneData *ZoneData) error {
	// Similar implementation for revive points
	zoneData.RevivePoints = make(map[int32]*RevivePoint)
	return nil // TODO: Implement
}

// ZoneData represents all data loaded for a zone
type ZoneData struct {
	ZoneID         int32
	Configuration  *ZoneConfiguration
	SpawnLocations map[int32]*SpawnLocation
	SpawnEntries   map[int32]*SpawnEntry
	NPCs           map[int32]*NPCTemplate
	Objects        map[int32]*ObjectTemplate
	Widgets        map[int32]*WidgetTemplate
	Signs          map[int32]*SignTemplate
	GroundSpawns   map[int32]*GroundSpawnTemplate
	Transporters   map[int32]*TransportDestination
	LocationGrids  map[int32]*LocationGrid
	RevivePoints   map[int32]*RevivePoint
	SpawnGroups    map[int32][]int32
}

// ZoneConfiguration represents zone configuration from database
type ZoneConfiguration struct {
	ZoneID             int32
	Name               string
	File               string
	Description        string
	SafeX              float32
	SafeY              float32
	SafeZ              float32
	SafeHeading        float32
	Underworld         float32
	MinLevel           int16
	MaxLevel           int16
	MinStatus          int16
	MinVersion         int16
	InstanceType       int16
	MaxPlayers         int32
	DefaultLockoutTime int32
	DefaultReenterTime int32
	DefaultResetTime   int32
	GroupZoneOption    int8
	ExpansionFlag      int32
	HolidayFlag        int32
	CanBind            bool
	CanGate            bool
	CanEvac            bool
	CityZone           bool
	AlwaysLoaded       bool
	WeatherAllowed     bool
}

// Template types for database-loaded spawn data
type NPCTemplate struct {
	ID              int32
	SpawnEntryID    int32
	Name            string
	Level           int16
	EncounterLevel  int16
	Model           string
	Size            float32
	HP              int32
	Power           int32
	Heroic          int8
	Gender          int8
	Race            int16
	AdventureClass  int16
	TradeskillClass int16
	AttackType      int8
	MinLevel        int16
	MaxLevel        int16
	EncounterType   int8
	ShowName        int8
	Targetable      int8
	ShowLevel       int8
	LootTier        int8
	MinGold         int32
	MaxGold         int32
	AggroRadius     float32
	CastPercentage  int8
	Randomize       bool
}

type ObjectTemplate struct {
	ID           int32
	SpawnEntryID int32
	Name         string
	Model        string
	Size         float32
	DeviceID     int32
	Icon         int32
	SoundName    string
}

type WidgetTemplate struct {
	ID              int32
	SpawnEntryID    int32
	Name            string
	Model           string
	WidgetType      int8
	OpenType        int8
	OpenTime        int32
	CloseTime       int32
	OpenSound       string
	CloseSound      string
	OpenGraphic     string
	CloseGraphic    string
	LinkedSpawnID   int32
	ActionSpawnID   int32
	HouseID         int32
	IncludeLocation bool
	IncludeHeading  bool
}

type SignTemplate struct {
	ID                int32
	SpawnEntryID      int32
	Name              string
	Model             string
	SignType          int8
	ZoneIDDestination int32
	WidgetID          int32
	Title             string
	Description       string
	ZoneX             float32
	ZoneY             float32
	ZoneZ             float32
	ZoneHeading       float32
	IncludeLocation   bool
	IncludeHeading    bool
}

type GroundSpawnTemplate struct {
	ID                int32
	SpawnEntryID      int32
	Name              string
	Model             string
	HarvestType       string
	NumberHarvests    int8
	MaxNumberHarvests int8
	CollectionSkill   string
	RespawnTimer      int32
}
